かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot でメール送信する Web アプリケーションを作る ( その8 )( メール送信画面の作成3 )

概要

Spring Boot でメール送信する Web アプリケーションを作る ( その7 )( メール送信画面の作成2 ) の続きです。

  • 今回の手順で確認できるのは以下の内容です。
    • メール送信画面の作成
    • 今回は Doma 2 を使用したメール保存処理を実装します。
  • Doma 2 は使い方を調査するのに苦労しましたが、コードジェネレータがあるのと、SQL ファイルが使えるのが魅力ですね。

ソフトウェア一覧

参考にしたサイト

  1. Welcome to Doma
    http://doma.readthedocs.org/ja/stable/

  2. Spring @Component vs @Configuration for basic configuration class
    http://stackoverflow.com/questions/18976634/spring-component-vs-configuration-for-basic-configuration-class

    • Doma 2 の Config インターフェースの実装クラスを Spring の DI コンテナにシングルトンで生成・管理してもらおうと思ったのですが、実装時に @Component と @Configuration のどちらのアノテーションを付与するのが良いのか迷ったので、その調査をした時に参照しました。
    • ComponentScan の対象にして他のクラスに @Autowired で DI するものは @Component を、@Bean を定義したり XMLベースの設定と同じことをする場合には @Configuration を、という感じでしょうか?
    • Doma 2 の Config インターフェースの実装クラスは Dao インターフェースの実装クラスに DI されるので、@Component アノテーションを使用することにします。
  3. IntelliJ Doma support plugin の GitHub サイト
    https://github.com/siosio/DomaSupport

  4. IntelliJ IDEA Plugins - Doma Support -> 0.3
    https://plugins.jetbrains.com/update/index?pr=idea&updateId=19563

    • IntelliJ Doma support plugin は Community Edition では使用できませんでした。サポートされていません。
  5. Welcome to Doma-Gen
    http://doma-gen.readthedocs.org/ja/stable/

  6. @AnnotateWithをメタアノテーションとして使用可能に
    http://d.hatena.ne.jp/taedium/20100130/p1

  7. SpringBootとDomaを連携する
    http://shinsuke789.hatenablog.jp/entry/2014/07/31/123800

    • Dao インターフェースの実装クラスを Spring の DI コンテナで管理させるための実装方法を参考にしました。
  8. SpringBoot+Doma2+Gradleを試してみた。
    http://qiita.com/nyasba/items/1e22c2401f3849f9071d

手順

1.0.x-make-mailsendform-saveemail ブランチの作成

  1. IntelliJ IDEA で 1.0.x-make-mailsendform-saveemail ブランチを作成します。

Doma 2 の Config インターフェースの実装クラスの作成

  1. 以下のドキュメントを参考にして、Doma 2 の Config インターフェースを実装します。

    設定
    http://doma.readthedocs.org/ja/stable/config/

    • Config インターフェースの実装クラスを Spring の DI コンテナにシングルトンとして生成・管理させるために @Component アノテーションを付加します。
    • dataSource は ApplicationConfig クラスに定義した dataSource Bean を @Autowired アノテーションで DI して使用します。
    • dialect は dialect Bean を生成する Java Configuration クラスを実装し、dialect Bean を @Autowired アノテーションで DI します。
  2. src/main/resources の下の application.properties を リンク先の内容 に変更します。

  3. src/main/java/ksbysample/webapp/email/config の下に DomaBeanConfig.java を作成します。作成後、リンク先の内容 に変更します。

  4. src/main/java/ksbysample/webapp/email/config の下に DomaConfig.java を作成します。作成後、リンク先のその1の内容 に変更します。

Community Edition に IntelliJ Doma support plugin はインストールできませんでした

Doma の開発に便利な IntelliJ IDEA 用のプラグインがあると書かれていたのでインストールしようと思いましたが、IntelliJ IDEA Plugins のプラグインのページ を見てみると、Community Edition は動作対象外でした。残念です。。。

f:id:ksby:20150502070522p:plain

Entity クラス、Dao インターフェース、SQL ファイルの作成

Doma には Doma-Gen というコードジェネレータが提供されていますので、それを利用して Entity クラス、Dao インターフェース、SQL ファイルを自動生成します。

  1. build.gradle を リンク先の内容 に変更します。

  2. Gradle projects View の左上にある「Refresh all Gradle projects」アイコンをクリックして、変更した build.gradle の内容を反映します。

  3. Gradle projects View から以下の場所にある gen タスクを実行します。

    f:id:ksby:20150502062518p:plain

    f:id:ksby:20150502003700p:plain

    • 一番最初の実行時はライブラリのダウンロードが行われます。上のキャプチャは何度か実行した後のもので、ライブラリのダウンロード時の様子はキャプチャするのを忘れていました。
  4. gen タスクが正常に終了すると、以下の画像の Entity クラス、Dao インターフェース、SQL ファイルが作成されます。

    f:id:ksby:20150502004303p:plain

  5. Dao インターフェースはこの後作成する Service クラスに @Autowired アノテーションで DI したいので、@Component アノテーションを付加しておきます。

  6. src/main/java/ksbysample/webapp/email/dao の下の EmailDao.javaリンク先のその1の内容 に変更します。

  7. src/main/java/ksbysample/webapp/email/dao の下の EmailItemDao.javaリンク先のその1の内容 に変更します。

  8. Doma-Gen で自動生成されたファイルは Git のバージョン管理対象として登録されていないので登録します。Project View でルートディレクトリを選択後、コンテキストメニューを表示して「Git」-「Add」を選択し、ファイルを追加します。

    f:id:ksby:20150505213249p:plain

Service クラスの作成、Controller クラスの変更

  1. src/main/java/ksbysample/webapp/email/web/mailsend の下に MailsendService.java を作成します。作成後、リンク先のその1の内容 に変更します。

  2. src/main/java/ksbysample/webapp/email/web/mailsend の下の MailsendController.javaリンク先の内容 に変更します。

動作確認 ( その1 )

  1. 動作確認します。Gradle projects View から bootRun タスクを実行して Tomcat を起動しようとしますが、org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mailsendController': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private ksbysample.webapp.email.web.mailsend.MailsendService ksbysample.webapp.email.web.mailsend.MailsendController.mailsendService; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mailsendService': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private ksbysample.webapp.email.dao.EmailDao ksbysample.webapp.email.web.mailsend.MailsendService.emailDao; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [ksbysample.webapp.email.dao.EmailDao] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} というエラーメッセージが出力されて起動しませんでした。どうも EmailDao を MailsendService クラスに DI できていないようです。原因を調査します。

  2. 調査した結果は以下の通りでした。

  3. 以下の実装に変更します。

  4. src/main/java/ksbysample/webapp/email の下に annotation.dao パッケージを作成します。

  5. src/main/java/ksbysample/webapp/email/annotation/dao の下に ComponentAndAutowiredDomaConfig.java を作成します。作成後、リンク先の内容 に変更します。

  6. src/main/java/ksbysample/webapp/email/dao の下の EmailDao.javaリンク先のその2の内容 に変更します。

  7. src/main/java/ksbysample/webapp/email/dao の下の EmailItemDao.javaリンク先のその2の内容 に変更します。

  8. Ctrl+F9 を押して build します。build\classes\main\generated\ksbysample\webapp\email\dao の下の EmailDaoImpl.java を見ると リンク先の内容 になっており、クラスに @Component アノテーションが、コンストラクタに @Autowired アノテーションが付加されていました。

  9. 再度 Gradle projects View から bootRun タスクを実行したところ、今度は Tomcat が正常に起動しました。動作確認を続けます。

動作確認 ( その2 )

  1. ブラウザを起動し http://localhost:8080/mailsend へアクセスします。以下の画像の値を入力後、「送信」ボタンをクリックします。

    f:id:ksby:20150505091551p:plain

  2. 「送信」ボタンをクリックすると以下の画像のエラーが発生しました。原因を調査します。

    f:id:ksby:20150505091723p:plain

  3. 調査した結果は以下の通りでした。

    • log4jdbc で出力された SQL を見てみると、以下の処理が実行されていました。
      1. Connection.setAutoCommit(false)
      2. Connection.prepareStatement(insert into email .....
      3. org.seasar.doma.jdbc.command.ModifyCommand.executeUpdate で PreparedStatement.executeUpdate() を呼び出して insert 文を実行
      4. Connection.prepareStatement(select currval('email_email_id_seq'))
      5. org.seasar.doma.jdbc.id.AbstractIdGenerator.getGeneratedValue で PreparedStatement.executeQuery() を呼び出して select currval('email_email_id_seq') を実行
      6. org.postgresql.util.PSQLException: ERROR: 本セッションでシーケンス"email_email_id_seq"のcurrvalはまだ定義されていません のエラーが発生
    • Google でエラーメッセージを検索してみると以下の記事がヒットしました。内容を見てみると、どうも SQL 発行と同時にコミットされてしまっているためらしいです。
    • DB の EMAIL テーブルを見るとデータが insert されていました。@Transactional アノテーションを付加しているのに効いていませんね。。。
      ※実際には何度か実行していたので、email_id は 1 ではなく 13 まで進んでいます。
      f:id:ksby:20150505095832p:plain
    • トランザクションが効いていないだけのように見えたので SpringBoot+Doma2+Gradleを試してみた。 の記事を参考に DataSourceBuilder.create().build()new TransactionAwareDataSourceProxy(DataSourceBuilder.create().build()) と変更してみたのですが、Tomcat を再起動すると java.sql.SQLException: The url cannot be null というエラーログが出力されて Tomcat が起動しませんでした。
    • 確かに dataSource Bean の中で DataSourceBuilder.create().build() で生成した dataSource を org.apache.tomcat.jdbc.pool.DataSource でキャストした後、設定されている値を System.out.println(dataSource.getUrl()); で出力してみたのですが、null でした。DataSourceBuilder.create().build() で生成した時に application.properties の設定が反映されていると思っていましたが、どうもそうではないようです。Service クラスに dataSource を @Autowired で DI して System.out.println(dataSource.getUrl()); で出力してみるとセットされているのですが。
    • dataSource への application.properties の設定の反映は dataSource Bean に AOP で処理を追加して行われているような気がするのですが、具体的な仕組みが分からないですね。。。 そのうち調べてみましょう。
    • まとめると、おそらく以下の仕組みになっていると思われます。
      • dataSource Bean 生成時点では Spring 管理のトランザクションに参加していません。よって、Doma 2 の Config インターフェースの実装クラスで dataSource をそのまま使用してもトランザクションが有効になりません。
      • 生成された dataSource に設定が反映されていれば new TransactionAwareDataSourceProxy(...) はエラーになりません。
      • dataSource Bean の生成時点では application.properties の設定は反映されていませんが、それ以外の場所で使用する場合には設定が反映されています。
    • DomaConfig クラスのフィールド dataSource にはセッターインジェクションで DI するように変更し、セッター内で new TransactionAwareDataSourceProxy(...) を呼び出して Spring 管理のトランザクションに参加させるようにしてみます。
  4. src/main/java/ksbysample/webapp/email/config の下の DomaConfig.javaリンク先のその2の内容 に変更します。

  5. 確認します。Run View で Ctrl+F5 を押して Tomcat を再起動します。

  6. ブラウザで http://localhost:8080/mailsend へアクセスします。以下の画像の値を入力後、「送信」ボタンをクリックします。

    f:id:ksby:20150505091551p:plain

  7. 今度はエラーは発生せず、メールも送信されました。

    f:id:ksby:20150505161548p:plain

    DB にも保存されていました。
    ※性別(sex)と項目(type)が保存されていないのは Form クラスと Entity クラスの型が違うためです。今回は Doma 2 を利用できるようにするための調査で時間がかかりすぎたので、次回に解決します。

    f:id:ksby:20150505161709p:plain f:id:ksby:20150505161812p:plain

  8. エラー発生時にロールバックされるかも確認します。最初に email, email_item テーブルからデータを削除します。

  9. Service クラス内で RuntimeException が発生するようにします。src/main/java/ksbysample/webapp/email/web/mailsend の下の MailsendService.javaリンク先のその2の内容 に変更します。

  10. Ctrl+F9 を押して build します。

  11. ブラウザで http://localhost:8080/mailsend へアクセスします。先程と同じデータを入力後、「送信」ボタンをクリックします。

  12. 予定通りエラーにはなったのですが、メールが送信されませんでした。Spring Loaded により Tomcat を再起動することなく反映されると思ったのですが、どうも反映されていないようです。

    f:id:ksby:20150505170042p:plain

  13. Run View で Ctrl+F5 を押して Tomcat を再起動します。

  14. 再度ブラウザで http://localhost:8080/mailsend へアクセスします。先程と同じデータを入力後、「送信」ボタンをクリックします。

  15. 今度は予定通りエラーになり、メールは送信されました。また DB を確認したところ、email, email_item どちらのテーブルにもデータは保存されていませんでした。

    f:id:ksby:20150505170142p:plain f:id:ksby:20150505170245p:plain

    ログにも rollback が出力されていました。

    f:id:ksby:20150505171131p:plain

  16. src/main/java/ksbysample/webapp/email/web/mailsend の下の MailsendService.java の実装を元に戻します。

  17. Run View で Ctrl+F2 を押して Tomcat を停止します。

commit、Push、Pull Request、マージ

  1. commit します。commit 時に Code Analysis のダイアログが表示されますが、Unused property の Warning だけなので「Commit」ボタンをクリックします。

  2. GitHub へ Push、1.0.x-make-mailsendform-saveemail -> 1.0.x へ Pull Request、1.0.x でマージ、1.0.x-make-mailsendform-saveemail ブランチを削除、をします。

Spring Boot で Doma 2 を使うためのメモ

  • Doma 2 を使用するための設定 ( build 時の SQL ファイルの出力先ディレクトリ等 ) を build.gradle に記述します。
  • dataSource は spring-boot-starter-data-jpa を build.gradle に記述した後、Java Configuration のクラスに dataSource Bean を実装したものを使用します。
  • Doma 2 の Config インターフェースの実装クラスを実装します。Spring の DI コンテナにシングルトンで生成・管理してもらうため、クラスには @Component アノテーションを付加します。また dataSource は dataSource Bean として実装したものをセッターインジェクションで DI します。インジェクション時には new TransactionAwareDataSourceProxy(...) を呼び出して dataSource を Spring 管理のトランザクションに参加させます。
  • コードジェネレータ Doma-Gen を利用して Entity クラス、Dao インターフェース、SQL ファイルを生成します。
  • Doma-Gen を利用するための設定 ( クラスの生成先のパッケージ、DB の設定 ) を build.gradle に記述します。
  • Doma 2 が apt で自動生成する Dao インターフェースの実装クラスを Spring の DI コンテナに生成・管理してもらうために、@AnnotateWith アノテーションで実装クラスに付加するアノテーションを指定します。最初に @AnnotateWith アノテーションを記述したアノテーションを作成しておき、各 Dao インターフェースには作成したアノテーションをクラスに付加します。
  • あとは Dao インターフェースを Service クラス等に @Autowired アノテーションで DI して使用します。
  • IntelliJ Doma support plugin は IntelliJ IDEA の Community Edition では使えません。。。

その他のメモ書き

次回は。。。

以下の点を調査・修正します。テストを書くのはその後の予定です。

  • create table 文のテーブル名/カラム名は英大文字で記述したのですが、Doma-Gen で生成された Entity クラスの中のテーブル名/カラム名や、pgAdmin Ⅲ 上に表示されるテーブル名/カラム名は英小文字で表示されていることに気づきました。PostgreSQL でのテーブル名/カラム名の命名ルールを確認してみます。
  • 現在 Service クラスのメソッドに @Transactional アノテーションを付加してトランザクションを開始していますが、AOPトランザクションが設定されるようにします。
  • dataSource と transactionManager を利用したいだけならば spring-boot-starter-data-jpa ではなく spring-boot-starter-data-jdbc でもよいような気がするので試してみます。
  • 性別(sex)と項目(type)が保存されない問題を解消します。
  • EMAIL_ITEM テーブルにデータを保存する処理で毎回インスタンスを生成するように実装しましたが、emailItemId をクリアすればよいだけのような気もするので試してみます。

ソースコード

application.properties

hibernate.dialect = org.hibernate.dialect.PostgreSQL9Dialect
doma.dialect = org.seasar.doma.jdbc.dialect.PostgresDialect

spring.jpa.hibernate.ddl-auto = none
spring.jpa.hibernate.naming_strategy = org.hibernate.cfg.ImprovedNamingStrategy

spring.velocity.enabled = false
spring.velocity.charset = UTF-8
  • doma.dialect を追加します。

DomaBeanConfig.java

package ksbysample.webapp.email.config;

import org.seasar.doma.jdbc.dialect.Dialect;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DomaBeanConfig {

    @Value("${doma.dialect}")
    private String domaDialect;

    @Bean
    public Dialect dialect()
            throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        return (Dialect)Class.forName(domaDialect).newInstance();
    }

}

DomaConfig.java

■その1

package ksbysample.webapp.email.config;

import org.seasar.doma.jdbc.Config;
import org.seasar.doma.jdbc.dialect.Dialect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;

@Component
public class DomaConfig implements Config {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private Dialect dialect;

    @Override
    public DataSource getDataSource() {
        return dataSource;
    }

    @Override
    public Dialect getDialect() {
        return dialect;
    }

}

■その2

package ksbysample.webapp.email.config;

import org.seasar.doma.jdbc.Config;
import org.seasar.doma.jdbc.dialect.Dialect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;

import javax.sql.DataSource;

@Component
public class DomaConfig implements Config {

    private DataSource dataSource;

    @Autowired
    private Dialect dialect;

    @Autowired
    public void setDataSource(DataSource dataSource) {
        this.dataSource = new TransactionAwareDataSourceProxy(dataSource);
    }

    @Override
    public DataSource getDataSource() {
        return this.dataSource;
    }

    @Override
    public Dialect getDialect() {
        return this.dialect;
    }

}
  • private DataSource dataSource; に付加していた @Autowired アノテーションを削除します。
  • セッターインジェクションするために @Autowired アノテーションを付加した setDataSource メソッドを追加します。セッター内でフィールド dataSource にセットする際に new TransactionAwareDataSourceProxy(...) を呼び出して dataSource を Spring 管理のトランザクションに参加させます。

build.gradle

buildscript {
    repositories {
        jcenter()
        // for org.springframework:springloaded
        maven { url "http://repo.spring.io/repo/" }
    }

    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.3.RELEASE")
        classpath("org.springframework:springloaded:1.2.3.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'spring-boot'
apply plugin: 'idea'

// for Doma 2
// JavaクラスとSQLファイルの出力先ディレクトリを同じにする
processResources.destinationDir = compileJava.destinationDir
// コンパイルより前にSQLファイルを出力先ディレクトリにコピーするために依存関係を逆転する
compileJava.dependsOn processResources

jar {
    baseName = 'ksbysample-webapp-email'
    version = '0.0.1-SNAPSHOT'
}

idea {
    module {
        inheritOutputDirs = false
        outputDir = file("$buildDir/classes/main/")
    }
}

configurations {
    domaGenRuntime
}

repositories {
    jcenter()
    // for org.seasar.doma:doma
    maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
}

dependencies {
    def jdbcDriver = "org.postgresql:postgresql:9.4-1201-jdbc41"

    // spring-boot-gradle-plugin によりバージョン番号が自動で設定されるもの
    // Appendix E. Dependency versions ( http://docs.spring.io/spring-boot/docs/current/reference/html/appendix-dependency-versions.html ) 参照
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.springframework.boot:spring-boot-starter-thymeleaf")
    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    compile("org.springframework.boot:spring-boot-starter-velocity")
    compile("org.springframework.boot:spring-boot-starter-mail")
    compile("org.codehaus.janino:janino")
    testCompile("org.springframework.boot:spring-boot-starter-test")
    testCompile("org.yaml:snakeyaml")

    // spring-boot-gradle-plugin によりバージョン番号が自動で設定されないもの
    compile("${jdbcDriver}")
    compile("org.seasar.doma:doma:2.2.0")
    compile("org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16")
    compile("org.apache.commons:commons-lang3:3.4")
    compile("org.projectlombok:lombok:1.16.2")
    testCompile("org.dbunit:dbunit:2.5.0")

    // for Doma-Gen
    domaGenRuntime("org.seasar.doma:doma-gen:2.2.0")
    domaGenRuntime("${jdbcDriver}")
}

bootRun {
    jvmArgs = ['-Dspring.profiles.active=develop']
}

test {
    jvmArgs = ['-Dspring.profiles.active=unittest']
}

// for Doma-Gen
task gen << {
    def rootPackageName = 'ksbysample.webapp.email'

    ant.taskdef(resource: 'domagentask.properties',
            classpath: configurations.domaGenRuntime.asPath)
    ant.gen(url: 'jdbc:postgresql://localhost/ksbyemail', user: 'ksbyemail_user', password: 'xxxxxxxx') {
        entityConfig(packageName: "${rootPackageName}.entity", useListener: false)
        daoConfig(packageName: "${rootPackageName}.dao")
        sqlConfig()
    }
}
  • configurations { ... } を追加します。
  • dependencies の以下の点を変更します。
    • def jdbcDriver = "org.postgresql:postgresql:9.4-1201-jdbc41" を追加します。
    • compile("org.postgresql:postgresql:9.4-1201-jdbc41")compile("${jdbcDriver}") へ変更します。
    • domaGenRuntime("org.seasar.doma:doma-gen:2.2.0"), domaGenRuntime("${jdbcDriver}") を追加します。
  • task gen << { ... } を追加します。
    • デフォルトでは entity, dao のパッケージが src/main/java/example の下に作成されますので、packageName パラメータで src/main/java/ksbysample/webapp/email の下に作成されるよう設定します。

EmailDao.java

■その1

@Dao
@Component
public interface EmailDao {

■その2

@Dao
@ComponentAndAutowiredDomaConfig
public interface EmailDao {
  • @Component@ComponentAndAutowiredDomaConfig に変更します。

EmailItemDao.java

■その1

@Dao
@Component
public interface EmailItemDao {

■その2

@Dao
@ComponentAndAutowiredDomaConfig
public interface EmailItemDao {
  • @Component@ComponentAndAutowiredDomaConfig に変更します。

MailsendService.java

■その1

package ksbysample.webapp.email.web.mailsend;

import ksbysample.webapp.email.dao.EmailDao;
import ksbysample.webapp.email.dao.EmailItemDao;
import ksbysample.webapp.email.entity.Email;
import ksbysample.webapp.email.entity.EmailItem;
import ksbysample.webapp.email.service.EmailService;
import ksbysample.webapp.email.util.VelocityUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MailsendService {

    @Autowired
    private EmailDao emailDao;

    @Autowired
    private EmailItemDao emailItemDao;

    @Autowired
    private VelocityUtils velocityUtils;

    @Autowired
    private EmailService emailService;

    @Transactional(rollbackFor = Exception.class)
    public void saveAndSendEmail(MailsendForm mailsendForm) {
        // 入力されたデータを EMAIL, EMAIL_ITEM テーブルに保存する
        saveEmail(mailsendForm);

        // メールを送信する
        sendEmail(mailsendForm);
    }

    public void saveEmail(MailsendForm mailsendForm) {
        // EMAIL テーブルに保存する
        Email email = new Email();
        BeanUtils.copyProperties(mailsendForm, email);
        emailDao.insert(email);

        // EMAIL_ITEM テーブルに保存する
        for (String item : mailsendForm.getItem()) {
            EmailItem emailItem = new EmailItem();
            emailItem.setEmailId(email.getEmailId());
            emailItem.setItem(Short.parseShort(item));
            emailItemDao.insert(emailItem);
        }
    }

    public void sendEmail(MailsendForm mailsendForm) {
        SimpleMailMessage mailMessage = MAIL001MailBuilder.build()
                .setForm(mailsendForm)
                .setVelocityUtils(velocityUtils)
                .setTemplateLocation("mail/MAIL001/MAIL001-body.vm")
                .create();
        emailService.sendSimpleMail(mailMessage);
    }

}

■その2

    @Transactional(rollbackFor = Exception.class)
    public void saveAndSendEmail(MailsendForm mailsendForm) {
        // 入力されたデータを EMAIL, EMAIL_ITEM テーブルに保存する
        saveEmail(mailsendForm);

        // メールを送信する
        sendEmail(mailsendForm);
throw new RuntimeException("メールは送信されるがDBには保存されないはず");
    }
  • saveAndSendEmail メソッドの最後に RuntimeException を throw します。

MailsendController.java

package ksbysample.webapp.email.web.mailsend;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/mailsend")
public class MailsendController {

    @Autowired
    private MailsendService mailsendService;

    @Autowired
    private MailsendFormValidator mailsendFormValidator;

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.addValidators(mailsendFormValidator);
    }

    @RequestMapping
    public String index(MailsendForm mailsendForm
            , Model model) {
        return "mailsend/mailsend";
    }

    @RequestMapping("/send")
    public String send(@Validated MailsendForm mailsendForm
            , BindingResult bindingResult
            , Model model) {
        if (bindingResult.hasErrors()) {
            return "mailsend/mailsend";
        }

        // 入力されたデータをDBに保存した後、メールを送信する
        mailsendService.saveAndSendEmail(mailsendForm);

        return "redirect:/mailsend";
    }

}
  • @Autowired アノテーションを付加して DI していた以下のフィールドは MailsendService クラスへ移動します。
    • private VelocityUtils velocityUtils;
    • private EmailService emailService;
  • send メソッド内に記述していたメール送信処理を MailsendService クラスへ移動し、send メソッドからは mailsendService.saveAndSendEmail(mailsendForm); を呼び出すように変更します。

ComponentAndAutowiredDomaConfig.java

package ksbysample.webapp.email.annotation.dao;

import org.seasar.doma.AnnotateWith;
import org.seasar.doma.Annotation;
import org.seasar.doma.AnnotationTarget;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@AnnotateWith(annotations = {
        @Annotation(target = AnnotationTarget.CLASS, type = Component.class),
        @Annotation(target = AnnotationTarget.CONSTRUCTOR, type = Autowired.class)
})
public @interface ComponentAndAutowiredDomaConfig {
}

EmailDaoImpl.java

package ksbysample.webapp.email.dao;

/** */
@org.springframework.stereotype.Component()
@javax.annotation.Generated(value = { "Doma", "2.2.0" }, date = "2015-05-05T04:01:34.940+0900")
public class EmailDaoImpl extends org.seasar.doma.internal.jdbc.dao.AbstractDao implements ksbysample.webapp.email.dao.EmailDao {

    static {
        org.seasar.doma.internal.Artifact.validateVersion("2.2.0");
    }

    private static final java.lang.reflect.Method __method0 = org.seasar.doma.internal.jdbc.dao.AbstractDao.getDeclaredMethod(ksbysample.webapp.email.dao.EmailDao.class, "selectById", java.lang.Long.class);

    private static final java.lang.reflect.Method __method1 = org.seasar.doma.internal.jdbc.dao.AbstractDao.getDeclaredMethod(ksbysample.webapp.email.dao.EmailDao.class, "insert", ksbysample.webapp.email.entity.Email.class);

    private static final java.lang.reflect.Method __method2 = org.seasar.doma.internal.jdbc.dao.AbstractDao.getDeclaredMethod(ksbysample.webapp.email.dao.EmailDao.class, "update", ksbysample.webapp.email.entity.Email.class);

    private static final java.lang.reflect.Method __method3 = org.seasar.doma.internal.jdbc.dao.AbstractDao.getDeclaredMethod(ksbysample.webapp.email.dao.EmailDao.class, "delete", ksbysample.webapp.email.entity.Email.class);

    /**
     * @param config the config
     */
    @org.springframework.beans.factory.annotation.Autowired()
    public EmailDaoImpl(org.seasar.doma.jdbc.Config config) {
        super(config);
    }
  • クラスに @org.springframework.stereotype.Component() が、コンストラクタ@org.springframework.beans.factory.annotation.Autowired() が付加されていることが確認できます。

履歴

2015/05/05
初版発行。