Java Tutorial 第四堂(3)Hibernate 與 JPA << 前情
在先前的課程中,我們 使用 spring-webmvc 框架 建立簡單的 Web 應用程式,使用 spring 相依注入 中進行依賴物件之注入,而在 Hibernate 與 JPA 中,既然認識了 ORM,那麼就也來使用 spring-orm 將之整合起來至 使用 spring 相依注入 撰寫的 DVDLibrary 專案之中吧!
練習 14:使用 spring-orm
這個練習要將練習 12 與練習 13 整合在一起。在 Lab 檔案的 exercises/exercise14 中有個 DVDLibrary 目錄,已經事先將練習 12 與練習 13 中一些可重用的程式碼(像是 Dvd.java、DvdDao.java 等)與設定檔(像是 build.gradle 等)準備好。spring-orm 提到了
LocalSessionFactoryBean
,用以簡化 Hibernate 的 SessionFactoy
之設定與建立,請開啟 dispatcher-servlet.xml,在 JDBCDataSource
的 bean
設定上增加 id
屬性為 dataSource
,同時也增加 LocalSessionFactoryBean
之設定:
...
<bean id="dataSource" class="org.hsqldb.jdbc.JDBCDataSource"
p:url="jdbc:hsqldb:file:src/main/resources/db/dvd_library"
p:user="codedata"
p:password="123456"/>
<bean class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan" value="tw.codedata" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">create</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
<prop key="show_sql">true</prop>
<prop key="format_sql">true</prop>
</props>
</property>
</bean>
...
在這邊,透過
LocalSessionFactoryBean
的 setDataSource
注入 DataSource
實例,packageToScan
設定了自動掃描實體(Entity)物件的套件來源,這樣就會自動尋找設定了 @Entity
的類別取得 ORM 資訊。
練習 13 中的
Dvd
、Director
、DvdDao
、Director
以及對應的 DAO 實作類別,都已經複製至練習 14 準備的專案之中,在動手修改 DvdController
之前,請先看一下原本的程式碼,例如 add
方法原先是這麼撰寫的:
...
Dvd dvd = new Dvd(title, year, duration, director);
getDvdDao().saveDvd(dvd);
m.addAttribute("dvd", dvd);
...
接下來你可能會打算將之改為:
...
DirectorDao directorDao = new DirectorDaoHibernateImpl(factory);
DvdDao dvdDao = new DvdDaoHibernateImpl(factory);
Director director = new Director("XD");
directorDao.saveDirector(director);
dvdDao.saveDvd(new Dvd("XD", 112, 1, director));
...
在 MVC 架構中,控制器應該是擔任請求轉發,而上頭的流程似乎混入了商務邏輯,也就是包括了
Director
、Dvd
物件之建立、設定相依關係,以及分別利用 DirectorDao
、DvdDao
分別儲存的邏輯,這並不建議,如果日後這些邏輯有了更複雜的變化,控制器就會開始面臨不斷的修改而增胖;另一方面,上面這種寫法,會讓控制器依賴在 DvdDao
、DirectorDao
等多個介面之上。
因此,建議建立一個新的商務物件來處理相關流程,例如若有個
DvdLibraryService
提供了 addDvd
與 allDvds
方法,就可以將 DvdController
如下修改:
package tw.codedata;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@Controller
public class DvdController {
private DvdLibraryService dvdLibraryService;
@Autowired
public void setDvdLibraryService(DvdLibraryService dvdLibraryService) {
this.dvdLibraryService = dvdLibraryService;
}
public DvdLibraryService getDvdLibraryService() {
return dvdLibraryService;
}
@RequestMapping("list")
public String list(Model m) {
m.addAttribute("dvds", getDvdLibraryService().allDvds());
return "list";
}
@RequestMapping("add")
public String add(
@RequestParam("title") String title,
@RequestParam("year") Integer year,
@RequestParam("duration") Integer duration,
@RequestParam("director") String director,
Model m) {
Dvd dvd = getDvdLibraryService().addDvd(title, year, duration, director);
m.addAttribute("dvd", dvd);
return "success";
}
}
以上也利用了 Spring 自動注入
DvdLibraryService
,如上修改之後,DvdController
仍維持基本的請求轉發職責,也僅依賴在 DvdLibraryService
之上,而 DvdLibraryService
只是包括了原先打算寫在 DvdController
的邏輯:
package tw.codedata;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DvdLibraryService {
private DirectorDao directorDao;
private DvdDao dvdDao;
@Autowired
public void setDirectorDao(DirectorDao directorDao) {
this.directorDao = directorDao;
}
@Autowired
public void setDvdDao(DvdDao dvdDao) {
this.dvdDao = dvdDao;
}
public DirectorDao getDirectorDao() {
return directorDao;
}
public DvdDao getDvdDao() {
return dvdDao;
}
public List<Dvd> allDvds() {
return getDvdDao().allDvds();
}
public Dvd addDvd(String title, Integer year, Integer duration, String directorName) {
Director director = new Director(directorName);
getDirectorDao().saveDirector(director);
Dvd dvd = new Dvd(title, year, duration, director);
getDvdDao().saveDvd(dvd);
return dvd;
}
}
為了讓 Spring 可以自動在
DvdLibraryService
中注入 DirectorDao
與 DvdDao
實例,你要在 DirectorDaoHibernateImpl
與 DvdDaoHibernateImpl
中加註 @Service
:
package tw.codedata;
import com.google.common.base.Optional;
import java.util.List;
import org.hibernate.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DirectorDaoHibernateImpl implements DirectorDao {
private SessionFactory sessionFactory;
@Autowired
public DirectorDaoHibernateImpl(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
...
其中
SessionFactory
的建構,也是透過 @Autowired
標註,讓 Spring 自動將 dispatcher-servlet.xml 中設定的 LocalSessionFactoryBean
注入。DvdDaoHibernateImpl
也是增加相同的標註:
package tw.codedata;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DvdDaoHibernateImpl implements DvdDao {
private SessionFactory sessionFactory;
@Autowired
public DvdDaoHibernateImpl(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
...
接下來就可以使用
gradle tomcatRunWar
來啟動程式,如果啟動時發生了 OutOfMemoryError: PermGen space 的錯誤,這是因為 JVM 的記憶體配置中,用來存放 .class 資訊的 PermGen 記憶體區段空間不足,可以在專案根目錄中建立一個 gradle.properties,撰寫以下資訊,增加 JVM 的 PermGen 區段空間大小:
org.gradle.jvmargs=-XX:MaxPermSize=256m