首先我們要下載 aopalliance.jar ,不然在執行事務時可能會發生ClassNotFoundException。可以到The Central Repository中搜尋最新版的jar,這是因為aopalliance.jar已經從NetBeans內建的Spring中分離了出來,要自已手動下載並引入至Library。如下圖:
在這裡,我們想要設計的構想其專案配置如下:
詳述如下:
- DaoClass.java:操控資料庫的CRUD(新增、查詢、修改、刪除)之Class。
- Guest.java:代表資料庫中的資料表之ORM(Object-Relational Mapping) 物件。
- ServiceClass.java:使用DaoClass進行事務(Transaction)處理的Class。
- DaoTest.java:用來測試DaoClass的測試JUnit。
- ServiceTest:用來測試ServiceClass的測試JUnit。
首先是在applicationContext.xml中配置要給Spring托管的內容,如連線池、SessionFactory、Annotation的支持設定、自動裝配Bean等等:
applicationContext.xml:
<?xml version='1.0' encoding='UTF-8' ?> <!-- was: <?xml version="1.0" encoding="UTF-8"?> --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd"> <!-- 設定dataSource連接池 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="com.mysql.jdbc.Driver" /> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/guest_database?zeroDateTimeBehavior=convertToNull" /> <property name="user" value="user" /> <property name="password" value="pass" /> <property name="maxPoolSize" value="20" /> <property name="minPoolSize" value="5" /> <property name="maxStatements" value="50" /> <property name="idleConnectionTestPeriod" value="300" /> </bean> <!-- 設定sessionFactory --> <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean" destroy-method="destroy"> <!-- 指定數據源,此處是C3P0連接池 --> <property name="dataSource" ref="dataSource" /> <!-- 指定ORM物件關聯表映射檔的設定檔位置 --> <property name="mappingResources"> <list> <value>OrmPackage/Guest.hbm.xml</value> </list> </property> <!-- 捨棄原hibernate.cfg.xml檔或 覆蓋原hibernate.cfg.xml檔的部份設定 --> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop> <prop key="hibernate.show_sql">true</prop> <!-- 不要用 <prop key="hibernate.current_session_context_class">thread</prop> --> <prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate4.SpringSessionContext</prop> <prop key="hibernate.query.factory_class">org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory</prop> </props> </property> </bean> <!-- 設定交易管理員transactionManager --> <bean name="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <!-- 對base-package下及其子資料夾偵測並自動裝配Bean --> <context:component-scan base-package="DaoPackage,ServicePackage" /> <!-- 要使用 @Transactional 時需要 --> <tx:annotation-driven /> </beans>在這裡要注意到,
<prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate4.SpringSessionContext</prop>不要設成
<prop key="hibernate.current_session_context_class">thread</prop>否則事務會沒有辦法正常運作,原因是當把事務交由Spring托管時,事務就不是以thread的方式運作了,而是以Spring自已的上下文方式運作。
接下來我們來進行DaoClass的實作,內容只是簡單的新增、修改、刪除、查詢 :
DaoClass:
package DaoPackage; import OrmPackage.Guest; import java.util.ArrayList; import java.util.List; import org.hibernate.Criteria; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.criterion.Restrictions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; @Repository public class DaoClass { //自動裝配SessionFactory @Autowired private SessionFactory sessionFactory; private Session currentSession() { return sessionFactory.getCurrentSession(); } //新增儲存 public void save(Guest guest){ currentSession().save(guest); } //修改 public void update(Guest guest){ currentSession().update(guest); } //刪除 public void delete(Guest guest){ currentSession().delete(guest); } //查詢 public Guest findById(String id){ Guest guest; guest = (Guest) currentSession().get(Guest.class, id); //在使用Spring的JUnit中其Transaction自動Rollback時,理應不會對資料庫造成影響, //但如果再Update、Delete等再使用Criteria的查詢方式的話,則就會對資料庫造成影響, //不知道為什麼 //Criteria criteria = currentSession().createCriteria(Guest.class); //criteria.add(Restrictions.eq("id", id)); //guest = (Guest) criteria.uniqueResult(); //guestList = currentSession().createCriteria(Guest.class).add(Restrictions.eq("id", id)).list(); return guest; } }在這邊findById()的實作沒有使用Criteria的方式,是因為它似乎會影響使用Srping管理JUnit和Transaction時,讓自動Rollback的不影響資料庫效果無效,即是說如果在JUnit裡先使用DaoClass.update()等會改變資料庫裡內容的方法,在用Criteria查詢並使用list()的話,資料庫的內容就會真得被影響,而正常來說Spring應該會在Test完後自動Rollback,不會影響資料庫原來內容才對,原因不明,如果有誰知道原因的話,還望不吝賜較。
接下來我們來設計用來測試DaoClass.java的JUnit項目,DaoTest.java:
DaoTest.java:
import DaoPackage.DaoClass; import OrmPackage.Guest; import java.util.Calendar; import java.util.Date; import org.hibernate.SessionFactory; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.*; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "file:web/WEB-INF/applicationContext.xml") @Transactional(propagation = Propagation.REQUIRED) //使用Spring管理交易 //Spring會在每一個測試後自動Rollback,即是說,測試完最後不會影響到資料庫 //不過如果在update等更新後用Criteria方式查詢的話,就會影響到資料庫,即是說Srping的 //自動Rollback失效,不知道為什麼 public class DaoTest { @Autowired //要測試的Class private DaoClass daoClass; @Autowired //使用SessionFactory來在測試中使用其他的CRUD功能, //例如在要測試DaoClass.update()的Test中,除了DaoClass.update(), //不要用到DaoClass.findById()等其他method private SessionFactory sessionFactory; //測試要用的參數 String id; String name; int age; Date birthDate; public DaoTest() { //設置初始參數 id = "123"; name = "Tom"; age = 55; Calendar birthDateCalendar = Calendar.getInstance(); birthDateCalendar.set(2005, Calendar.JANUARY, 5); birthDate = birthDateCalendar.getTime(); } @Test public void saveTest() { //模擬儲存 Guest guestToBeSave = new Guest(id, name, age, birthDate); daoClass.save(guestToBeSave); //取出剛儲存進去的Guest Guest guestToBeChecked = (Guest) sessionFactory.getCurrentSession().get(Guest.class, id); //是否取出的Guest之各項值等於剛儲存的Guest assertEquals(id, guestToBeChecked.getId()); assertEquals(name, guestToBeChecked.getName()); assertEquals(age, guestToBeChecked.getAge()); assertEquals(birthDate, guestToBeChecked.getBirthDate()); } @Test public void updateTest() { String updatedName = "345"; //先存一個等下要update的Guest sessionFactory.getCurrentSession().save(new Guest(id, name, age, birthDate)); //儲完後先再取出來 Guest guest = (Guest) sessionFactory.getCurrentSession().get(Guest.class, id); //測試是否是剛儲存的那一個Guest assertEquals(id, guest.getId()); assertEquals(name, guest.getName()); //測試DaoTest的update方法 guest.setName(updatedName); daoClass.update(guest); //再一次取出被改過Name的Guest Guest updatedGuest = (Guest) sessionFactory.getCurrentSession().get(Guest.class, id); //測試取出的Guest的Name是否真得被改過了 assertEquals(updatedName, updatedGuest.getName()); } @Test public void deleteTest() { //先存一個要delete的Guest sessionFactory.getCurrentSession().save(new Guest(id, name, age, birthDate)); //再將之取出後delete掉 Guest guestToBeDeleted = (Guest) sessionFactory.getCurrentSession().get(Guest.class, id); daoClass.delete(guestToBeDeleted); //確認是否真得被刪除了,即是說找用同樣的id(主鍵)找不到Guest了 assertNull((Guest) sessionFactory.getCurrentSession().get(Guest.class, id)); } @Test public void findByIdTest() { //先存一個要Guest sessionFactory.getCurrentSession().save(new Guest(id, name, age, birthDate)); //測試DaoClass的findById() Guest guest = daoClass.findById(id); //測試是否Guest有正確被找到 assertNotNull(guest); //測試被找到的Guest是否就是剛存的那一個Guest assertEquals(id, guest.getId()); assertEquals(name, guest.getName()); assertEquals(age, guest.getAge()); assertEquals(birthDate, guest.getBirthDate()); } @BeforeClass public static void setUpClass() { } @AfterClass public static void tearDownClass() { } @Before public void setUp() { } @After public void tearDown() { } }
接著我們實作一個幸行事務的Class,ServiceClass.java:
ServicecClass:
package ServicePackage; import DaoPackage.DaoClass; import OrmPackage.Guest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Service @Transactional(propagation = Propagation.REQUIRED) //使用Spring管理交易 public class ServiceClass { @Autowired //使用Spring注入DaoClass private DaoClass daoClass; //儲存一個Guest並刪除另一個Guest public void saveOneAndDeleteAnother(Guest guestToBeSave, Guest guestToBeDelete) { daoClass.save(guestToBeSave); daoClass.delete(guestToBeDelete); } }最後我們再實作用來測試ServiceClass的JUnit項目,ServiceTest:
ServiceTest.java:
import OrmPackage.Guest; import ServicePackage.ServiceClass; import java.util.Calendar; import java.util.Date; import org.hibernate.SessionFactory; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.*; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "file:web/WEB-INF/applicationContext.xml") //使用Spring管理交易,主要是因為這裡用到了SessionFactory,主要是給SessionFactory用的, //其實更完美正確的單元測試應該是要連ServiceClass裡面用到的DaoClass都用Mockito去模擬 //才對,這樣才更具單元解耦性 @Transactional(propagation = Propagation.REQUIRED) public class ServiceTest { @Autowired private ServiceClass serviceClass; @Autowired private SessionFactory sessionFactory; public ServiceTest() { } @Test public void saveOneAndDeleteAnotherTest() { //設置測試要用的參數 //設置saveOneAndDeleteAnother()要儲存的Guest String idForGuestToBeSave = idForGuestToBeSave = "123"; String nameForGuestToBeSave = nameForGuestToBeSave = "Tom"; int ageForGuestToBeSave = ageForGuestToBeSave = 55; Calendar birthDateCalendarForGuestToBeSave = Calendar.getInstance(); birthDateCalendarForGuestToBeSave.set(2005, Calendar.JANUARY, 5); Date birthDateForGuestToBeSave = birthDateForGuestToBeSave = birthDateCalendarForGuestToBeSave.getTime(); Guest guestToBeSave = guestToBeSave = new Guest(idForGuestToBeSave, nameForGuestToBeSave, ageForGuestToBeSave, birthDateForGuestToBeSave); //設置saveOneAndDeleteAnother要刪除的Guest String idForGuestToBeDelete = idForGuestToBeDelete = "345"; String nameForGuestToBeDelete = nameForGuestToBeDelete = "Mary"; int ageForGuestToBeDelete = ageForGuestToBeDelete = 65; Calendar birthDateCalendarForGuestToBeDelete = Calendar.getInstance(); birthDateCalendarForGuestToBeDelete.set(2005, Calendar.JANUARY, 6); Date birthDateForGuestToBeDelete = birthDateForGuestToBeDelete = birthDateCalendarForGuestToBeDelete.getTime(); Guest guestToBeDelete = guestToBeDelete = new Guest(idForGuestToBeDelete, nameForGuestToBeDelete, ageForGuestToBeDelete, birthDateForGuestToBeDelete); //先把saveOneAndDeleteAnother要刪除的Guest儲存起來 sessionFactory.getCurrentSession().save(guestToBeDelete); //開始測試 //測試是否能取出要剛除的Guest,即是有無正確地儲存起來 assertEquals(guestToBeDelete, sessionFactory.getCurrentSession().get(Guest.class, guestToBeDelete.getId())); //測試saveOneAndDeleteAnother()方法 serviceClass.saveOneAndDeleteAnother(guestToBeSave, guestToBeDelete); //測試saveOneAndDeleteAnother()有無把要儲存的Guest儲存起來 assertEquals(guestToBeSave, sessionFactory.getCurrentSession().get(Guest.class, guestToBeSave.getId())); //測試saveOneAndDeleteAnother()是否有把要刪除的Guest刪除掉 assertNull(sessionFactory.getCurrentSession().get(Guest.class, guestToBeDelete.getId())); } @BeforeClass public static void setUpClass() { } @AfterClass public static void tearDownClass() { } @Before public void setUp() { } @After public void tearDown() { } }
這樣我們的建製就完成了,我們可以在NetBeans中對專案按右鍵選擇“Test”進行測試,注意右下角的Rollback提示訊息,在JUnit中,如果採用Sprnig托管測試及事務(Transaction)的話,會在一個Test測完後將事務Rollback,將資料庫回復原樣,不會動到資料庫原來的內容,十分方便。如果要不Rollback,想要更新到資料庫的話,可以如下設定:
@Rollback(true)
最後附上源始碼:
MySpringHibernateTranscation.7z
參考資料:
作者已經移除這則留言。
回覆刪除