2013年3月30日 星期六

如何快速在aws 上架網站

這個寫起來可能落落長,不過順序大致上是幾項:

1 申請帳號,填寫信用卡
2 啟動一個ec2 的服務,選預設免錢的那個
3 security group 要設定好http 也要allow ,不然無法存取
4 在裡面安裝apahce server

sudo yum install httpd

5 設定eip 的mapping (可設可不設)
6 放一個歡迎頁html 在  /var/www/html

細節慢慢再寫


安裝tomcat

sudo yum install tomcat6
這樣就裝好了。

記得aws console 中 security group 中要選custom tcp ,然後open 8080 port.
可以在webapps建立一個專案,放一個index.html

如何啟動關閉tomcat
sudo service tomcat6 start
sudo service tomcat6 stop

*建立windows server+sql server的時候,記得在 mgmt studio 中改回mixed mode,否則一般的使用者無法登入。

2013年3月24日 星期日

當Spring MVC+Hibernate搭配的時候出現 No Hibernate Session bound to thread


SpringMVC + Hibernate Error: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here.


這個惱人的問題在於Spring MVC 的設定方式。
首先你的DispatchServlet 對應的xml先找出來
你應該有設定
<context:component-scan base-package="tw" />
這一類的東西。
如果tw是你的根目錄,Spring 就會開始掃瞄所有有annotation的bean並加入管理。
你應該有另一個xml 是管理Spring 交易的,它那邊也會有一個
<context:component-scan base-package="tw" />
因為第一個scan已經把所有的bean load進來了,在交易管理的那個scan變成完全沒作用了。

這樣會導致Spring 的交易沒有被注入,也就是你會因此看到這個錯誤訊息。

解決方法就是把二者的類別錯開,盡量讓spring mvc的scan只掃controller的目錄,不要去掃到Service 層的類別。

讓交易的那個xml去掃service那一層,這樣交易才會被注進去。

2013年3月16日 星期六

Spring 最輕鬆的完整配置方式

本文目的是要去除所有無謂的宣告,並且將多datasource與交易的設定全都建立起來,aop 的功能也要建立起來。

一般工程師抗拒使用Spring的原因通常只是為了設定很麻煩。如果能直接new 為何要設定半天 ? 不過如果連new 都不用,也不用寫什麼設定檔,不是更好嗎 ?

我看過一些實際專案的Spring的XML,有些團隊好像覺得用了Spring就是了不起的事,但是當專案越來越龐大的時候,過多的xml 絕對是痛苦的來源。真正好的設計是在專案中驗證出來的,一開始的易用,並不代表當需求開始增加時,仍能保持易用性。這個時候就可以看的出來設計上有沒有問題。

以下的配置以Spring 3.2為基礎。

學一個東西先看怎麼用,好用的話再參考。

Action



@Controller
@Scope("prototype")

public class EmeaAction {
@Autowired
EmeaService emeaService;

public String index() {

System.out.println(emeaService.list());
return "success";
}  
}

對一般人來說不清楚的就是紅色的Annotation的部份。先不說細節。

Service


@Service
public class EmeaService {
@Autowired
private QuotationDAO quotationDAO ;

@Transactional(value="quo", readOnly=true)
public List<QuoQuotationMain>  list() {
return quotationDAO.listAll();
}
}


到目前為止應該都還ok吧,Service layer的部份又看到一次 @Autowired,  這很明白的表示,請spring 自動幫我注入這個dao。其它細節也不先說。

DAO


@Repository
public class QuotationDAO { 

@Autowired

private SessionFactory sessionFactory2;

public List<QuoQuotationMain> listAll() {
return sessionFactory2.getCurrentSession().createQuery("from    QuoQuotationMain").list();
}
}


好,程式的部份就這樣子了。應該還算好用吧,如果覺得很難用的話,那就回去原來的世界吧…只是原來的寫法不可能更少,如果原來的Java寫法程式可以這麼少的話,還有誰想要學Spring。先來解釋一下這三層最上面會出現的註示。

@Controller=@Component
@Service =@Component
@Repository=@Component

這算什麼解釋啊 ? 因為這些注釋都是語意而已,其實都是代表 @Component,那@Component是做什麼的,這樣宣告可以讓Spring知道說「喔,這個就是Bean,我會自動幫你們註冊,然後id就和類別一樣吧,只是前一個字要小寫」等於Spring 自動幫你產生xml了,於是乎我們就等同有了三個已註冊的bean 在xml裡了。

@Controller 代表他是MVC 中的C,控制流程為主,下面的scope=prototype就是說每次呼叫都會產生一個新的。不信的話你在方法中去印this,得到的物件其實每次都會不同。

@Service 是中間層的處理方式,也包含了交易的部份,與dao溝通的部份。
@Repository 就是指DAO

對程式設計人員來說,你所要知道的就只要這樣子就好了。就算是Grails來說,寫法也不會再更簡單了,頂多它不用宣告Annotation,以命名方式為主,但是有一點註解其實還是比較好,一看到就知道這個類別負責的功能是哪一塊。

好了,再來你應該會想像有很多的xml要設定。但其實不會,你看上面的程式之後,一定要設定的就是datasource與session factory,這個不可能主動幫你建吧,所以這個要怎麼設定?



<bean id="dataSource1" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/deu"></property>
</bean>
<bean id="dataSource2" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/sap"></property>
</bean>


你的jndi是什麼就自己改吧。


<bean id="sessionFactory1" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource1" />
<property name="hibernateProperties" value="classpath:hibernate.cfg.xml" />  
<property name="packagesToScan" value="tw.entity.deu" />
</bean>
<bean id="sessionFactory2" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource2" />
<property name="hibernateProperties" value="classpath:hibernate.cfg.xml" />
<property name="packagesToScan" value="tw.entity.emea" />
</bean>


目前不到20 行,而且這二個東西和db 有關,通常很少再動了。就算dba 逼你連到另一個schema 來存取資料,只要加一個sessionFactory和一個dataSource即可。然後在service layer中,value記得用你取的新名字即可。整個衝擊修改過程不用1分鐘。

packagesToScan 這個好像有一點陌生? 他可是專案膨風後的救星,因為再也不用「多一個表格 就要再去xml宣告一次」了。我一直覺得多一個資料庫表格就要在Spring設定一次真的是很無聊的事,現在你可以叫session factory 去掃那個資料夾就好。專案中多一個表格的衝擊就是 : 打開編輯器,自動從table建出annotation 式的entity後結束。當你一次要弄20幾個table出來一個一個宣告這些無意義的程式時,就知道這個屬性真棒!

好了,那xml 裡還有什麼呢?

還有交易的部份。這個訂xml也不過份啦,而且日後就很少需要改。我把會動到的用粗體標示。


<bean id="transactionManager1" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory1"></property>
<qualifier value='deu'></qualifier>
</bean>
<bean id="transactionManager2" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory2"></property>
<qualifier value='quo'></qualifier>
</bean>


交易管理員會以aop 的方式插到service layer去,一個交易管理員對應一個session factory(剛設定過了),一個session factory對應一個datasource(也設定好了),所以交易的部份現在都ready了,專案膨風後也不太需要再做設定。

你說他用aop 的部份,剛剛怎麼看不出來?


Service


@Service
public class EmeaService {
@Autowired
private QuotationDAO quotationDAO ;

@Transactional(value="quo", readOnly=true)
public List<QuoQuotationMain>  list() {
return quotationDAO.listAll();
}
}


@Transactional(value="quo", readOnly=true)這裡就是叫Spring 用aop 的方式在前、後自動補上交易管理,並且使用的是quo 這個交易管理員(transactionManager2啦,上面有)。

這邊全部已經設定完了。在xml 的最上面加上一些tag告訴Spring你要用掃瞄的方式注入。這三行一定要寫。



<context:component-scan base-package="tw"><
/context:component-scan>

<aop:aspectj-autoproxy />
<tx:annotation-driven />


第一行是叫Spring用掃的方式注入。
第二行是啟動aop +annotation來注入 ( 在之前講過不再多提)
第三行是啟動annotation+aop注入交易管理員。

再整理一次。xml 只要寫這三行,以及

  • Datasource
  • Transaction manager
  • Session Factory

之後xml 幾乎就不太需要再更動了。
至於新的Action的話,目前我是用Struts2,改成Spring後再分享一下以Spring 為 MVC 要如何設定。

2013年3月9日 星期六

Spring 宣告式交易管理

聽起來很華麗,用起來也很過癮,但是一開始沒有搞的很清楚,加上又找不到詳細的解釋,在Stackoverflow上問了問題,再參照其它的文章,才搞懂他真正的用法。


本文章的關鍵字是 : Transaction propagation , declarative transaction,Spring宣告式交易管理。



首先先講測試的架構,都是用annotation來做。前面有介紹過方法了。

1。 發起方是一個action裡的method,它會call UserService一個 testAddUserButAddCountryFail的方法。

2. 在這個service裡面,注了一個dao,與另一個service。

3. 這個testAddUserButAddCountryFail方法內我們做二件事: 首先呼叫dao 來insert 一筆資料,這邊我們會讓它insert 成功。再來用另一個service來呼叫,這個service裡面我們會對交易的方式做測試,都是要讓他fail ,但是要看他對testAddUserButAddCountryFail 產生什麼影響。

看程式比較容易:

UserService裡


     @Resource
   private UsersDAO userDao;
     @Resource
    CountryService countryService;


     @Transactional(value="deu"   )
    public void testAddUserButAddCountryFail(){
      userDao.addUser();
      countryService.addCountry();   
  }

CountryService 裡


     @Resource
   private CountryDAO countryDao;

@Transactional(value="deu"  )
public void addCountry(){  
countryDao.addCountryFail();
}

測試 一 : CountryService中的@Transactional 不做什麼設定,預設就會是串在一起的交易。所以當 countryService.addCountry(); 裡面報錯時,exception丟出來 後,整個交易都會失敗,所以user不會新增成功。

如果我們把  countryService.addCountry(); 包try catch不讓它擴散呢?  不行噢,當程式呼叫 countryService.addCountry(); 時,addCountry已被視為是同一個交易,當countryService.addCountry(); 裡出問題自動rollback 時,已經馬上影響到addUser了,也就是原來add的user在一出問題時已經被rollback了。當程式回到 testAddUserButAddCountryFail 你想再處理時已經來不及了。

測試二 : 將


@Transactional(value="deu"  )
public void addCountry(){  
countryDao.addCountryFail();
}



改為


@Transactional(value="deu", propagation=Propagation.REQUIRES_NEW  )
public void addCountry(){  
countryDao.addCountryFail();
}


我們想要知道,根據Spring的文件來說,這邊會變成一個新的交易,我們要讓它出錯,看影響的狀況如何。我們預期它是一個新的交易,所以它出錯不會影響到新增的User.

結果呢…新增的user還是被rollback了。這一點讓我百思不解。後來研究了網站上的說明,才發現 addCountry雖然是一個新的交易,它自己也rollback了,但是它的exception還是會丟回外部的交易(也就是原來addUser開的那一個),因為我們在

@Transactional(value="deu"  )

public void testAddUserButAddCountryFail(){
   userDao.addUser();
   countryService.addCountry();    
 }

中沒有做任何處理,這個exception間接導致了 testAddUserButAddCountryFail 也rollback了。

解決方式 : 在    countryService.addCountry(); 加try catch,白話來說就是 : 它是一個獨立的交易,要是裡面出錯了,它的exception不對我( testAddUserButAddCountryFail ) 產生任何影響。

這邊懂了之後,順便講一下Spring的術語:

當程式一進去 testAddUserButAddCountryFail 時,因為我們有@Transactional ,所以這邊是一個新的交易,叫outer transaction,也就是外面的交易。當我們  countryService.addCountry(); 的時候,因為  addCountry() 裡也是一個交易,這邊叫 inner transaction (內部) 。整個測試案例就是在測試,inner一但有exception時如何對 outer 產生影響。

propagation=Propagation.REQUIRES_NEW會把inner transaction變成一個獨立交易,不寫的話會變成inner的一部份。雖然透過 propagation=Propagation.REQUIRES_NEW變成了一個獨立交易,但是outer還是要對它丟出的例外做處理,都不處理的話,這個例外會影響到原有的交易行為,變成跟著也rollback了。

我這邊順便再貼一下JB Nizet 的回應,他講的夠清楚了。

You got it right. 

If the second service uses REQUIRED, both services are in the same transaction, and the transaction will be marked for rollback if an exception is thrown by the second (or first) service. Catching the exception in the first service won't change anything: the transaction is marked for rollback already. 

If the second service uses REQUIRES_NEW and throws an exception, it has its own transaction, which will be rollbacked. But the exception, as any other exception, will propagate to the caller (the first service). So if it doesn't catch it, its transaction will also be rollbacked. – JB Nizet