2008年7月30日 星期三

Migration File Naming Scheme

2.1 版之前的 Rails,Migration File 命名方式採用 Numeric Naming Scheme:
001_xxx.rb
002_xxx.rb
...

2.1 版之後的 Rails,Migration File 命名方式採用 Timestamped Naming Scheme:
YYYYMMDDHHMMSS_xxx.rb
YYYYMMDDHHMMSS_xxx.rb
...

資料庫會多出一個新的 schema_migrations 表格,記錄目前執行過的 Migration。

如果不喜歡這種 Timestamp 方式,可以在 environment.rb 檔案設定:

config.active_record.timestamped_migrations = false

2008年7月14日 星期一

Notepad++

最近開始改用 Notepad++,也在 Port of Ruby Blue Theme 發現一個移植自 TextMate 的 Style,改過來之後發現還蠻好看的。

2008年7月2日 星期三

Spring Framework HibernateTemplate 的 initialize 方法

Spring Framework 提供了 HibernateTemplate,封裝 Hibernate Session 重要的 API,簡化程式的撰寫。

Session 的 load 與 get 有差異,HibernateTemplate 封裝的 load 與 get 當然就有差異,看 HibernateTemplate 的原始程式就很清楚:
public Object load(final Class entityClass, final Serializable id, final LockMode lockMode)
throws DataAccessException {

return executeWithNativeSession(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException {
if (lockMode != null) {
return session.load(entityClass, id, lockMode);
}
else {
return session.load(entityClass, id);
}
}
});
}

public Object get(final Class entityClass, final Serializable id, final LockMode lockMode)
throws DataAccessException {

return executeWithNativeSession(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException {
if (lockMode != null) {
return session.get(entityClass, id, lockMode);
}
else {
return session.get(entityClass, id);
}
}
});
}
可是有一件事很有趣,原本使用 Session/Transaction 這樣寫的程式碼:
try
{
tx = session.beginTransaction();
book = (Book) session.load(Book.class, bookID);
Hibernate.initialize(book);
tx.commit();
}
catch (Exception e)
{
if (tx != null) tx.rollback();
e.printStackTrace();
}

不能直接改成底下使用 HibernateTemplate 的寫法:
try
{
book = (Book) hibernateTemplate.load(Book.class, bookID);
hibernateTemplate.initialize(book);
}
catch (Exception e)
{
e.printStackTrace();
}

原因就是 HibernateTemplate 的 Javadoc 所說的:

This class can be considered as direct alternative to working with the raw Hibernate3 Session API (through SessionFactory.getCurrentSession()). The major advantage is its automatic conversion to DataAccessExceptions as well as its capability to fall back to 'auto-commit' style behavior when used outside of transactions. Note that HibernateTemplate will perform its own Session management, not participating in a custom Hibernate CurrentSessionContext unless you explicitly switch "allowCreate" to "false".

可是呢,即使把 allowCreate 設定為 false,還是會有問題:

Caused by: org.hibernate.HibernateException: load is not valid without active transaction

也就是沒有把 load 包在 Transaction 裡頭。

可是如果還要寫出 Transaction 的程式碼,那就麻煩了。

所以呢,就是透過 Spring Framework 提供的 AOP,掛上 Transaction,這時候就不必去設定什麼 allowCreate,直接就可以正常運作了!

2008年7月1日 星期二

Hibernate Session 的 get 與 load

Hibernate Session 為了節省一次 Database Roundtrip,所以:
  • load 只是產生一個 Proxy,或是直接取用目前 Session 中已有的 Cache,不會真的連到 Database
  • get 才會真的連到資料庫,取得一個 Fully Initialized POJO
以前只注意到 get 找不到資料的時候會回傳 null,可是 load 不會。不管 Database 有沒有那筆紀錄,load 的傳回值一定不是 null。詳細探究原因,才知道 load 根本不會殺到 Database,也就沒有機會給 null。

org.hibernate.Session 的 Javadoc 說法是:
  • load:Return the persistent instance of the given entity class with the given identifier, assuming that the instance exists. You should not use this method to determine if an instance exists (use get() instead). Use this only to retrieve an instance that you assume exists, where non-existence would be an actual error.
  • get:Return the persistent instance of the given named entity with the given identifier, or null if there is no such persistent instance. (If the instance, or a proxy for the instance, is already associated with the session, return that instance or proxy.)
這邊看不出為什麼 get 不會拿到 Proxy,但是從 org.hibernate.impl.SessionImpl 的程式碼可以看到:
public Object load(Class entityClass, Serializable id, LockMode lockMode) throws HibernateException {
return load( entityClass.getName(), id, lockMode );
}

public Object load(String entityName, Serializable id, LockMode lockMode) throws HibernateException {
LoadEvent event = new LoadEvent(id, entityName, lockMode, this);
fireLoad( event, LoadEventListener.LOAD );
return event.getResult();
}

public Object get(Class entityClass, Serializable id, LockMode lockMode) throws HibernateException {
return get( entityClass.getName(), id, lockMode );
}

public Object get(String entityName, Serializable id, LockMode lockMode) throws HibernateException {
LoadEvent event = new LoadEvent(id, entityName, lockMode, this);
fireLoad(event, LoadEventListener.GET);
return event.getResult();
}

LoadEventListener 有 LOAD 與 GET 兩種情形。

org.hibernate.event.LoadEventListener 的程式碼中可以看出:
public static final LoadType GET = new LoadType("GET")
.setAllowNulls(true)
.setAllowProxyCreation(false)
.setCheckDeleted(true)
.setNakedEntityReturned(false);

public static final LoadType LOAD = new LoadType("LOAD")
.setAllowNulls(false)
.setAllowProxyCreation(true)
.setCheckDeleted(true)
.setNakedEntityReturned(false);

其中,GET 的時候,setAllowProxyCreation 的參數是 false,應該就表示 GET 的時候不允許產生 Proxy 物件。setAllowNulls 的參數是 true,這應該就是為什麼找不到資料的時候可以回傳 null 的原因吧!