2012年4月21日土曜日

Hibernate でデータアクセス(2)

前回の続きです。

サービスレイヤー
DAO レイヤーと連携しながら、コントローラーの要求に応じたサービスを提供するレイヤーです。DAO レイヤーと同様(というか Spring Framework における開発の基本?)、インターフェースを定義し、その実装クラスを作ります。

AccountService.java(インターフェース)
新規アカウントを作成する makeAccount() と UserProfile を取得する getProfile() を定義してます。

package wrider.service;

import java.util.Map;

import wrider.model.IdCard;
import wrider.model.UserProfile;

public interface AccountService {
  
  public Map makeAccount(UserProfile userProfile);
  
  public UserProfile getProfile(IdCard idCard);

}

AccountServiceImpl.java(実装クラス)
@Service アノテーションでこのクラスがサービスコンポーネントであることを示しています。尚、ProfileManager インターフェースの実装クラス ProfileManagerImple には、@Repository アノテーションを付けてリポジトリーコンポーネントであることを示しています。

[servlet-name]-servlet.xml で <context:component-scan/> が有効になっている場合、Spring Framework は、@Autowired されたフィールドの型に該当するインターフェースの実装クラスを探して DI します。実装クラスに付けた @Service や @Repository は、この時の目印になります。

ProfileManager フィールドに @Autowired を付けて、 ProfileManagerImpl を DI しています。

makeAccount()メソッドは、コントローラーから受け取った UserProfile オブジェクトを profileManager の callAddUserProfile()メソッドに渡し、処理結果を Map で受け取ります。getProfile()メソッドは、コントローラーから IdCard オブジェクトを受け取り、profileManager の getProfile()を呼び出す際、パラメーターとして渡します。

package wrider.service;

import java.util.Map;

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

import wrider.dao.ProfileManager;
import wrider.model.IdCard;
import wrider.model.UserProfile;

@Service
public class AccountServiceImpl implements AccountService {
  
  @Autowired
  private ProfileManager profileManager;
  
  public Map<String, Object> makeAccount(UserProfile userProfile) {
    return this.profileManager.callAddUserProfile(userProfile);
  }
  
  public UserProfile getProfile(IdCard idCard) {
    return this.profileManager.getUserProfile(idCard);
  }
  
}

コントローラー
テーブルスキーマの定義から DAO レイヤー、サービスレイヤーと遡って、ようやくコントローラーに辿りつきました。『CGLIB Proxy で捕捉エリアを拡大』までに散々手を加えた AccountController クラスに、先述した AccountService を利用するメソッドを追加します。

AccountController.java(抜粋)
まず、@Autowired で AccountService を DI しています。/account/login.html および /account/register.html に対する GET リクエストを受けた場合は、単純に各 URL に対応するビュー名を返すだけです。
package wrider.controller;
    :
@Controller
@SessionAttributes({"idCard", "userProfile"})
public class AccountController {
  
  @Autowired
  private AccountService accountService;
  
  @ModelAttribute("idCard")
  public IdCard makeIdCard() {
    return new IdCard();
  }
  
  @ModelAttribute("userProfile")
  public UserProfile makeUserProfile() {
    return new UserProfile();
  }
    :
  @RequestMapping(value="/account/login.html", method=RequestMethod.GET)
  public String login() {
    return "account/login";
  }
  
    :
  @RequestMapping(value="/account/register.html", method=RequestMethod.GET)
  public String register() {
    return "account/registrationForm";
  }
    :
}

login()メソッド:
ログインフォームからの POST リクエストで実行される login()メソッドは、引数として フォームへの入力データを含む IdCard とバリデーション結果を格納する BindingResult のほか、@ModelAttribute でアノテートされた UserProfile オブジェクトを受け取ります。これに accountService.getProfile(idCard) の実行結果がバインドされます。UserProfile オブジェクトは、@SessionAttributes に登録されているため、セッションが有効である間は維持されます。認証成功後、取得した UserProfile を ModelAndView オブジェクトに登録して /welcome/home.html にリダイレクトします。

@RequestMapping(value="/account/login.html", method=RequestMethod.POST)
  public ModelAndView login(
      @ModelAttribute("userProfile") UserProfile userProfile,
      @Valid IdCard idCard,
      BindingResult br) {
    
    ModelAndView mav = new ModelAndView();
    
    if (br.hasErrors()) {
      mav.getModel().putAll(br.getModel());
      mav.setViewName("account/login");
    }
    else {
      userProfile = accountService.getProfile(idCard);
      if (userProfile == null) {
        mav.setViewName("account/login");
      }
      else {
        mav.addObject("userProfile", userProfile);
        mav.setViewName("redirect:../welcome/home.html");
      }
    }
    return mav;
  }

尚、引数の順序について若干の注意が必要です。リファレンス“16.3.3.1 Supported method argument types”の最後に『BindingResult(またはError)引数は、モデルオブジェクト(引数)の直後に続かなければならない。Spring は、先行する各モデルオブジェクトのために BindingResult インスタンスを生成する』という旨が書いてあります。確かに、BindingResult が @Valid でアノテートした引数の後に記述されていないと「バリデーション結果を入れる場所が無い」と怒られます。

register()メソッド:
accountService.makeAccount(userProfile) の実行結果は、HashMap型で返されます。キー「result」には成功(true)/失敗(false)、「newpid」には割り当てられた pid(profileId)が入ります。アカウント作成が成功した場合は /welcome/home.html にリダイレクトし、失敗の場合は再度、アカウント作成フォームを表示します。

@RequestMapping(value="/account/register.html", method=RequestMethod.POST)
  public ModelAndView register(
      @Valid UserProfile userProfile, 
      BindingResult br) {
    
    ModelAndView mav = new ModelAndView();
    
    if (br.hasErrors()) {
      mav.getModel().putAll(br.getModel());
      mav.setViewName("account/registrationForm");
    }
    else {
      
      HashMap<String, Object>notice = (HashMap<String, Object>)accountService.makeAccount(userProfile);
      
      if (Boolean.parseBoolean(notice.get(Items.RESULT).toString())) {
        mav.setViewName("redirect:/welcome/home.html");
      }
      else {
        mav.setViewName("account/registrationForm");
      }
      
    }
    
    return mav;
  }

次回は、Hibernate, c3p0 関連の設定を予定しています。

0 件のコメント:

コメントを投稿