首先我們要下載 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
參考資料:




作者已經移除這則留言。
回覆刪除