顯示具有 Hibernate 標籤的文章。 顯示所有文章
顯示具有 Hibernate 標籤的文章。 顯示所有文章

2015-09-22

在 Hibernate hbm.xml 裡使用 enum 類型儲存資料

用 Annotation 習慣之後,遇到既有專案得用 hbm.xml 來設定 Mapping,還真是有點辛苦啊。

下面用簡單的 enum 來練習。
public enum Status {

  ENABLE, DISABLE;
  
}
如果什麼都不做,只是單純的用 property 設定。
<property name="status"/>
產生的 SQL 就是基本型。
status varbinary(255) null
但是最大的問題在於資料庫存的是 enum 的順序(在 Annotation 就是用 EnumType.ORDINAL),也就是 ENABLE 是 0,DISABLE 是 1。

這種作法有兩個缺點,第一個資料庫裡的 0 或 1 沒有任何意義,必須搭配程式才能知道 0 是 ENABLE,1 是 DISABLE;第二個缺點是 enum 裡的 ENABLE 或 DISABLE 不能改變順序,因為連帶會影響到資料庫。

2015-06-09

為什麼 LocalSessionFactoryBean 回傳的是 SessionFactoryImpl?

我習慣使用 org.hibernate.tool.hbm2ddl.SchemaExport 來產出 SQL。
Configuration cfg = new Configuration();
cfg.configure(); // 預設讀取 hibernate.cfg.xml
SchemaExport export = new SchemaExport(cfg);
export.setOutputFile("src/main/database/all.sql");
export.setDelimiter(";");
export.setFormat(true);
export.create(true, false);
但這種作法有一個前提,就是要有 hibernate.cfg.xml,可是隨著 Spring 的不斷滲透,hibernate.cfg.xml 慢慢的已經不見了,其設定值都進到 Spring XML 設定檔裡了。

之前為了產出 SQL,還特地為此建立 hibernate.cfg.xml,但這又會造成與 Spring XML 不同步的困擾。

2014-07-04

在 Hibernate Criteria 裡使用 SQL Function

想要執行類似以下的 SQL 語法。
select * from book order by length(title) asc;
在 Hibernate Criteria 裡要怎麼使用呢?在 Criteria、Property 與 Projections 裡怎麼也找不到相關的 API,最後 Google 才說,要用 @Formula 建立虛擬欄位。

Hibernate 提供的 @Formula 有兩種用途,第一是使用資料庫的運算,而非 JVM,第二是虛擬欄位,也就是不存在資料庫中的欄位,由其他欄位計算產生出來的結果。

2013-11-30

從 Spring + Hibernate 到 Spring Data JPA

抱著 Spring + Hibernate 的架構好長一段時間了,最近發現一個有趣的東西,Spring Data JPA,目前穩定版本是 1.4.2。

Duplicated code!!!

我的 Spring + Hibernate 的標準作法。

UserDao.java
public interface UserDao {

  User get(int uid);

  User getByAccount(String account);

  void add(User cmd);

  void update(User cmd);

  void delete(User cmd);

  List<User> list(UserQueryVO queryVO, int start, int pageSize);

  int count(UserQueryVO queryVO);
}

2013-03-29

Hibernate 無法讀取 MySQL 的 0000-00-00

使用 Hibernate 讀取既有的資料庫,在遇到日期格式的欄位值為 0000-00-00 時發生錯誤。

事實上,這是 JDBC 發生的錯誤,不是 Hibernate。

2012-11-09

在 Spring MVC 3.1.2.RELEASE 與 Hibernate 4.1.7.Final 實作變動數量的值物件(Variable Component)

值物件?以前在 Hibernate 叫做 Component,現在 JPA 叫做 Embeddable,或者從 Annotation 的角度叫做 Element。

簡單講就是沒有 Primary Key(PK) 的資料,必須依附在有 PK 的資料下。

舉例來說,每個人(Person)有好幾種地址(Address),住家地址、公司地址、戶籍地與出生地等等,這些地址資料只對一個人有意義,脫離這個人就沒有存在的必要,或者說不能與其他人共用,既然不能共用,那就沒必要有 PK,與這個人共存亡。

2012-10-30

在 Spring 3.1.2.RELEASE 使用多個資料庫的宣告式交易管理(Multiple Databases with Annotation-based Declarative Transaction Management)

一般的系統只會用到一個資料庫,Spring 引進宣告式交易管理後,設定變得很簡單。

Spring 設定檔。
<context:component-scan base-package="idv.neil.model,idv.neil.service,idv.neil.dao" />

<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
  <property name="configLocation" value="classpath:hibernate.cfg.xml" />
</bean>

<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
  <property name="sessionFactory" ref="sessionFactory" />
</bean>

<tx:annotation-driven />
先用 context:component-scan 告訴 Spring 所有的 Annotation 在哪些 package 裡。

然後建立 Hibernate SessionFactory,這是給 Dao 用的。

再建立 TransactionManager,這是給 Service 用的。

最後用 tx:annotation-driven 將全部串起來。

在 Hibernate 4.1.7.Final 使用 Annotation 的 Composite identifier

參考官方文件 5.1.2.1. Composite identifier

有三種作法,特色 annotation 分別為 @EmbeddedId、@Id 與 @IdClass。
  • @EmbeddedId 是建議的作法。
  • @Id 不是標準,只有 Hibernate 支援。
  • @IdClass 是 EJB 年代的東西。

2012-08-29

Hello Hibernate Envers 4.1.6.Final

Envers 不知道在什麼時候加入 Hibernate?文件在 3.3.0.GA 有看到,而 Maven 裡最早的版本則是 3.5.0.Final。

聽說 Envers 之前是 jBoss 的成員,後來 Hibernate 進入 jBoss,Envers 就搬到 Hibernate 裡了。

講一堆,Envers 到底是什麼?簡單一句話,就是歷史版本。

一些系統有時候會要求,將文件的每次修改都存下來,供以後追蹤,就像是 SVN 這樣的功能,而 Envers 就是在 Hibernate 提供這樣的功能(驚嚇中...)。

快速使用 Envers 三步驟:
  1. 在 hibernate.cfg.xml 裡設定一些 listener,用來監測是否要另存歷史版本
  2. 在 entity 裡加上 @Audited,表示要存歷史版本
  3. 產生必要的 table

2011-11-02

Hibernate 連線 SQLServer 2000 出錯

真的是太久沒用 SQLServer 2000 了!

正確應該說是 JDBC 連線 SQLServer 2000 出錯,錯誤訊息為 java.sql.SQLException: [Microsoft][SQLServer 2000 Driver for JDBC]Error establishing socket.。

只要將 SQLServer 2000 安裝 SP4 就可以了!
2011/11/2 上午 11:19:27 org.apache.catalina.core.StandardWrapperValve invoke
嚴重的: Servlet.service() for servlet jsp threw exception
java.sql.SQLException: [Microsoft][SQLServer 2000 Driver for JDBC]Error establishing socket.
    at com.microsoft.jdbc.base.BaseExceptions.createException(Unknown Source)
    at com.microsoft.jdbc.base.BaseExceptions.getException(Unknown Source)
    at com.microsoft.jdbc.base.BaseExceptions.getException(Unknown Source)
    at com.microsoft.jdbc.sqlserver.tds.TDSConnection.<init>(Unknown Source)
    at com.microsoft.jdbc.sqlserver.SQLServerImplConnection.open(Unknown Source)
    at com.microsoft.jdbc.base.BaseConnection.getNewImplConnection(Unknown Source)
    at com.microsoft.jdbc.base.BaseConnection.open(Unknown Source)
    at com.microsoft.jdbc.base.BaseDriver.connect(Unknown Source)
    at java.sql.DriverManager.getConnection(DriverManager.java:582)
    at java.sql.DriverManager.getConnection(DriverManager.java:154)
    at org.hibernate.connection.DriverManagerConnectionProvider.getConnection(DriverManagerConnectionProvider.java:110)
    at org.hibernate.jdbc.ConnectionManager.openConnection(ConnectionManager.java:417)
    at org.hibernate.jdbc.ConnectionManager.getConnection(ConnectionManager.java:144)
    at org.hibernate.jdbc.JDBCContext.connection(JDBCContext.java:129)
    at org.hibernate.transaction.JDBCTransaction.begin(JDBCTransaction.java:57)
    at org.hibernate.impl.SessionImpl.beginTransaction(SessionImpl.java:1290)
    ...
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
    at org.apache.coyote.http11.Http11AprProcessor.process(Http11AprProcessor.java:864)
    at org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:579)
    at org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:1665)
    at java.lang.Thread.run(Thread.java:662)

2011-04-19

@ManyToMany - delete inverse side object

假設兩個有著多對多關係的 class 如下:
@Entity
@SuppressWarnings("serial")
public class Author extends CommonVersionEntity {

 private Set<Book> bookSet = new HashSet<Book>();

 @ManyToMany
 @JoinTable(name = "author_book", joinColumns = {
  @JoinColumn(name = "author_id")
 }, inverseJoinColumns = {
  @JoinColumn(name = "book_id")
 }, uniqueConstraints = @UniqueConstraint(columnNames = {
   "author_id", "book_id"
 }))
 @ForeignKey(name = "author_book_fk", inverseName = "book_author_fk")
 public Set<Book> getBookSet() {
  return bookSet;
 }

 public void setBookSet(Set<Book> bookSet) {
  this.bookSet = bookSet;
 }
}

@Entity
@SuppressWarnings("serial")
public class Book extends CommonVersionEntity {

 private Set<Author> authorSet = new HashSet<Author>();

 @ManyToMany(mappedBy = "bookSet")
 public Set<Author> getAuthorSet() {
  return authorSet;
 }

 public void setAuthorSet(Set<Author> authorSet) {
  this.authorSet = authorSet;
 }
}

Google App Engine 簡介

用 Java 寫一個網路應用程式( web app)需要哪些東西?
  1. 首先,當然要會寫 Java,還有要懂得 JavaEE 在 web 的兩大主角,Servlet 與 JSP,如果不想用這麼原始的方式,還要學會 Spring MVC 或者 Struts 之類的 web framework。
  2. 現在應該不會有任何一個 web app 沒用到資料庫,所以還需要 JDBC,或者較好用但複雜的 Hibernate,以及一個真正的 database server,最常用的當然是 mysql 了。
  3. 程式寫完之後,我們需要一個可以執行程式的地方,那就是 web container(或 web server),一般都是用 Tomcat。
  4. 再來就是一台真正的機器,用來執行 Tomcat。
  5. 最後就是對外網路與網域名稱。

2011-02-09

The 5 most critical things to consider for proper collections usage in Hibernate

資料來源:Hibernate mapped collections performance problems

假設單向一對多的關聯:

<class name="Library">
 <id name="id">
  <generator class="sequence"/>
 </id>
 <set name="visits">
  <key column="library_id" not-null="true"/>
  <one-to-many class="Visit"/>
 </set>
</class>
<class name="Visit">
 <id name="id">
  <generator class="sequence"/>
 </id>
 <property name="personName"/>
</class>


在子物件的數量非常龐大時:

問題一、因為是用 Set,所以當增刪子物件時,Hibernate 為了確保 Set 的 unique,必須載入整個 collection,逐一檢查唯一性,出現效能瓶頸。


2011-02-08

到底是誰用了fetch="select"!

今天在處理效能問題時遇到一個 Hibernate 設定造成的效能問題,在一對多的關聯裡使用了以下的設定,導致 N+1 Select 的問題。

<set name="childred" inverse="true" lazy="false" fetch="select" cascade="all-delete-orphan">

在資料量不大時,這問題不明顯,但是當一次要 select 幾千筆資料時,每一筆資料都會另外執行一個 select 來取出 children 資料,這時 N+1 Select 就影響整個效能。

可以在下 HQL 時使用 eager fetch 來解決這個問題。

select p from Parent p join fetch children c where ....;


2007-03-24

[Note] Hibernate Object States

Hibernate物件的狀態有四種:transient、persistent、detached與removed。

transient -> persistent
  • save()
  • saveOrUpdate()
Hibernate也可以用merge()得到相同的結果,merge()是回傳一個新的obj,不是原本傳進去的obj(參考[Note] Hibernate Update or Merge)。

db -> persistent
  • get()
  • load()
  • Query.xxx()
persistent -> detached
  • evict()
  • clear()
  • close()
detached -> persistent
  • update()
  • saveOrUpdate()
  • merge()
persistent -> removed
  • delete()

[Note] Hibernate Get or Load

在Hibernate 2.x的年代,要用id取出一個物件,都是用load(),但是這個method在找不到時會丟exception,還是那種checked的。

來到Hibernate 3.x的年代,load()還是會丟exception,只不過Hibernate的exception都變成unchecked了;但是另外多了一個get(),一樣用id,最明顯差別在找不到時不會丟exception了,而僅僅只是回傳null。

事實上,get()與load()還有一個很大的差異:get()把整個obj都抓進來,不管是從db還是session cache,而load()只是回傳一個proxy,一個lazy properties只有在access時才會initialize的proxy,所以透過load()取得的obj只能用在session裡,一離開session,access未initialize的property都會出lazy exception。

所以疑問來了,load()進來的proxy用在什麼情況?先離題一下:為什麼只有session.delete(obj),沒有session.delete(id)?都是要刪除了,還要用id先把obj抓進來再殺不是多此一舉嗎?為什麼不直接用id去殺就好?因為有Hibernate interceptor的關係!

回歸正題,load()的proxy有兩個主要用途:
  • 要刪除某個物件時,用proxy就好,不用把整個物件抓進來。
  • 要建立association時,也是用proxy就好,像是child.setParent(proxyParent)這樣,因為child只需要parent的id就好,不需要其他欄位,所以proxy什麼都沒有,只有id。

[Note] Hibernate Update or Merge

Hibernate物件的狀態有四種:transient、persistent、detached與removed(參考[Note] Hibernate Object States),這邊要講的是從detached到persistent這條路。

不明確的方法就不管了,指的就是transition persistent:A被persitent,導致A的關係人B也persistent,所以重點還是在怎麼讓A被persitent。

明確的方法有三種:update()、lock()與merge()。

update()
最熟悉的當就是update()了。
session.update(detachedObj);
但是有一點要特別注意的,就是這個動作一定會觸發一次sql update,即使detached obj沒有被修改,原因在obj是detached,所以Hibernate無從得知是否被修改過了,所以只好統統update。
可以設定select-before-update="true"要求Hibernate先去select一次舊資料來比對決定是否要update,有點囉唆,但好處有二:
  • 當obj很大時(就是column很多時,多少算多?五十個以上吧),可以避免無謂的update。
  • 當upadte會觸發Hibernate interceptor或db trigger時,導致不想要的副作用時。
lock()
有點麻煩的方式,因為有個參數要設定LockMode。
session.lock(detachedObj, LockMode.NONE);
  • LockMode.NONE:狀態從detached變成persistent,但是Hibernate不管這個detached obj有沒有被修改過,一律假設是乾淨未被修改過的,但是在lock之後的修改就會sync到db了,若lock之前有修改這時也會sync到db裡。
  • 其他的LockMode就會檢查detached obj有沒有被修改過了。
merge()
精彩的來了!
session.merge(detachedObj);
merge()主要用來對付一種討厭的情況或錯誤:org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [Book#10]
程式大概都是長這樣:
session = sf.openSession();
tx = session.beginTransaction();
List list = session.createCriteria(Book.class).list();
// 給你掛掉
session.update(aBook);
tx.commit();
aBook是detached obj,而list中也包括了與aBook相同id的另一個物件,所以在list之後,相同id的物件已經存在session cache中了,這時要把aBook再persistent到session裡就會掛掉,因為同一id的物件在同一session裡只能存有一份。
而一般在update detached obj之前會把同id的原物件取出來有幾種可能:做log或做欄位正確性檢查,因此在update之前執行get/list就無法避免了。
之前的作法就是用session.evict()將新取出來的物件踢出session,然後再update,但是這樣有個缺點,就是evict的動作得發生在business layer,造成business與dao的coupling。
session = sf.openSession();
tx = session.beginTransaction();
List list = session.createCriteria(Book.class).list();
// 麻煩的作法
session.evict(list);
// 沒事了
session.update(aBook);
tx.commit();
現在救星來了,改用merge()吧。
session = sf.openSession();
tx = session.beginTransaction();
List list = session.createCriteria(Book.class).list();
// 安全過關
aBook = session.merge(aBook);
tx.commit();
// 這個要特別注意
return aBook;
merge時,Hibernate會將detached obj的資料都copy到persistent obj裡,然後回傳persistent obj,所以detached obj還是detached。
所以回傳的那一筆跟原本在session cache裡那一筆是同一筆,而被merge的那一筆跟回傳的與原本在session cache裡那兩筆還是不同的。
被merge的 != 原本在session cache裡那一筆
被merge的 != 回傳的那一筆
原本在session cache裡那一筆 == 回傳的那一筆
例外狀況:
  • 當session cache裡沒有相同id的物件時,去db裡面找。
  • 當db裡找不到時,就新增一筆新的。
  • 當被merge的obj不是detached而是transient時,就新增一筆新的。

[Note] Hibernate Flush

Hibernate預設的flush時機:
  • tx commit時
  • 查詢前
  • 明確呼叫session.flush()
在tx commit時flush為Hibernate的特色,JDBC在commit時並不會flush。

可以用session.setFlushMode()設定flush執行時機:
  • FlushMode.AUTO:預設值,執行時機為tx commit時、查詢前與明確呼叫session.flush()
  • FlushMode.COMMIT:執行時機為tx commit時與明確呼叫session.flush(),少了查詢前這一項,會出現查詢到『stale data』的情況,因為在執行查詢時,session的cache可能已經有些被修改但未flush到db的資料,這些異動資料是查詢不到的。
  • FlushMode.MANUAL:執行時機只有明確呼叫session.flush()
頻繁的flush會影響效能,另外使用預設flush時,modify-query-modify-query的使用方式也是效能殺手。

flush之後,可以從db查詢的到,表示已經進db了,但是若在flush之後發生錯誤導致rollback,那些已經被flush進db的能否rollback呢?經過測試,flush進db的資料是可以被rollback的

2007-03-23

[Gain] Extend transaction boundaries to business layer with Spring 2.0 AOP

一般的架構為web -> Business -> Dao,合理的transaction boundaries應該要放在Business上,畢竟commit/rollback unit應該是一次Business operation,而不僅僅只是一次dao operation。
但使用Hibernate或沒有container的架構中,transaction不是來自Hibernate session就是JDBC connection(事實上是同一個啦),所以若要將transaction boundaries延伸到Business Layer,勢必變成Business Layer會看到Hibernate session,這又是我們不願意見到的。
但有了Spring AOP之後,這問題似乎可以解決了。
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="load*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="*" propagation="REQUIRED" read-only="true"/>
<!-- SystemService -->
<tx:method name="login" propagation="REQUIRED" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="allServiceMethods" expression="execution(* com.xxx.service.*Service.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="allServiceMethods"/>
</aop:config>

首先,透過allServiceMethods規範所有的Business Layer method都必須經過txAdvice定義的transactionManager。
<aop:pointcut id="allServiceMethods" expression="execution(* com.xxx.service.*Service.*(..))"/>表示在com.xxx.service這個package下名稱為Service結尾的所有class的所有method。
<tx:advice id="txAdvice" transaction-manager="transactionManager">...</tx:advice>定義了以method name為規則的transaction條件。
最後用<aop:advisor advice-ref="txAdvice" pointcut-ref="allServiceMethods"/>將上面兩項綁起來。

[Debug] Hibernate Many-to-many Collection

環境介紹

使用Hibernate處理many-to-many,主要物件有二:Category與Document。

Category與Document關聯為單向many-to-many。

程式如下:
// Domain objects
public class Category extends CommonBean {
private String name;
private Collection<Book> books = new ArrayList<Book>();
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinTable(joinColumns = {
@JoinColumn(name = "categoryId")
}, inverseJoinColumns = {
@JoinColumn(name = "bookId")
})
public Collection<Book> getBooks() {
return books;
}
// ...
}

狀況描述
背景:假設Category A裡已經有三筆Book,分別為Book A、Book B與Book C。

執行:建立一Category B,並將Category A裡的三筆Book複製到Category B裡。

Category bCategory = new Category();
bCategory.setName("Science");
HibernateUtil.doAdd(bCategory, sf);

bCategory.setBooks(aCategory.getBooks());
HibernateUtil.doUpdate(bCategory, sf);

預期:Category A有三筆Book,Category B也有三筆Book,均為Book A、Book B與Book C,也就是表格Category_Book應該要有六筆資料。

結果:Category A沒有任何Book,Category B有三筆Book,分別為Book A、Book B與Book C,即表格Category_Book只有三筆資料。

問題所在
Category A從Hibernate取出來時,books那個Collection A實際上是Hibernate自訂的型別(custom Collection implementation),Hibernate之所以使用自訂的Collection是為了保留Entity與Database的關聯,用來進行像是dirty check之類的工作,所以這個Collection A等於是和Database裡的那三筆資料代表。

然後,Category A離開了session,變成detached,Category B出現了,Category A的Collection A整個被塞到Category B裡,問題就在這個動作。

最後Category B與資料庫同步了,這時候Hibernate發現Category B裡的Collection A是『自家人』,這時候有趣的事情發生了,Hibernate在更新detached的many-to-many時,都是先將『既有』的關聯全刪掉,再插入新的關聯,所以原本那三筆row就因此被刪掉了(因為Collection A那個『自家人』知道有這三筆資料的存在),最後再塞進三筆與Category B有關連的Book資料( [Gain] Hibernate Delete Orphan)。

解決方法
不可以將整個Collection塞進來,得一本一本Book搬進來,這樣Category A還是Collection A,Category B則是用新的Collection B,只不過這兩個Collection裡的都是Book A、Book B與Book C,唯一的差異就是Collection有兩個。

bCategory.getBooks().addAll(aCategory.getBooks());
延伸狀況
如果bCategory.setBooks(aCategory.getBooks())這個動作事發生在session裡,則會產生org.hibernate.HibernateException: Found shared references to a collection: collection.Category.books。由此可知,Hibernate是反對Collection共用的,即使內容一樣。