javascript
SSH框架之Spring4专题4:Spring与DAO
- 本專題內容主要包含兩部分:Spring所使用的操作數據庫的技術之一,JDBC模版的使用;另一部分則為Spring對于事務的管理。
- Spring與Dao部分,是Spring的兩大核心技術loC與AOP的經典應用體現:
- 對于JDBC模版的使用,是loC的應用,是將JDBC模版對象注入給Dao層的實現類。
- 對于Spring的事務管理,是AOP的應用,將事務作為切面織入到Service層的業務方法中。
1 Spring與JDBC模版
- 為了避免直接使用JDBC而帶來的復雜且亢長的代碼,Spring提供了一個強有力的模版類--JdbcTemplate來簡化JDBC操作。并且,數據源DataSource對象和模版JdbcTemplate對象均可通過Bean的形式定義在配置文件中,充分發揮了依賴注入的威力。
1.1 導入Jar包
1、Spring的JDBC Jar包:
2、Spring的事務Jar包:1.2 搭建測試環境
1、定義實體類User:
public class User {private Integer id;private String username;private int age;//setter and getter()@Overridepublic String toString() {return "User [id=" + id + ", username=" + username + ", age=" + age + "]";} }2、定義數據庫以及表:
public interface IUserDao { void insertUser(User user); void deleteUser(int id); void updateUser(User user); String selectUsernameById(int id); List<String> selectUsernamesByAge(int age); User selectUserById(int id); List<User> selectAllUsers(); }
3、定義IUserDao:4、初步定義UserDaoImpl:
- 這里僅僅定義一個UserDaoImpl類實現了IUserDao接口,但不具體寫每個方法的方法實現。保持默認即可。后面會逐個通過Jdbc模版來實現。 public class UserDaoImpl implements IUserDao{
@Override
public void insertUser(User user) {
}
//...
}
5、定義IUserService:
public interface IUserService { void addUser(User user); void removeUser(int id); void modifyUser(User user); String findUsernameById(int id); List<String> findUsernamesByAge(int age); User findUserById(int id); List<User> findAllUsers(); }6、定義UserService:
public class UserServiceImpl implements IUserService{ private IUserDao dao; public void setDao(IUserDao dao) {this.dao = dao; } @Override public void addUser(User user) {dao.insertUser(user); } //... }7、定義測試類MyTest:
public class MyTest { private IUserService service; @Before public void setUp() {String resource = "applicationContext.xml";ApplicationContext ac = new ClassPathXmlApplicationContext(resource);service = (IUserService) ac.getBean("myService"); } @Test public void testInsert() {User user = new User("張三", 23);service.addUser(user); } }1.3 數據源的配置
- 使用JDBC模版,首先需要配飾號數據源,數據源直接以Bean的形式配置在Spring配置文件中。根據數據源的不同,其配置方式也不同。下面主要講解三種常用數據源的配置方式:
1、Spring默認的數據源;
2、DBCP數據源;
3、C3P0數據源;1.3.1 Spring默認的數據源DriverManagerDataSource
- Spring默認的數據源為DriverManagerDataSource,其有一個屬性DriverClassName,用于接收DB驅動。
- Ctrl + O查看類結構以及源碼:
- DriverManagerDataSource類繼承自AbstractDriverBasedDataSource。其有三個屬性用于接收連接數據庫的URL、用戶名和密碼。
- Ctrl + O查看父類的類結構和源碼:
<!-- 配置Spring默認數據源 --> <bean id="myDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver"></property><property name="url" value="jdbc:mysql:///test"></property><property name="username" value="root"></property><property name="password" value="02000059"></property> </bean>1.3.2 DBCP數據源BasicDataSource
- DBCP,DataBase Connection Pool,是apache下的項目,使用該數據源,需要導入兩個Jar包。它們在Spring依賴庫的解壓目錄的org.apache.commons目錄中dbcp與pool子包中。
- com.springsource.org.apache.commons.dbcp-1.2.2.osgi.jar;
- com.springsource.org.apache.commons.pool-1.5.3.jar;
- DBCP數據源是BasicDataSource,Ctrl + O查看其類結構可以看到,其有driverClassName、url、username、password四個DB連接屬性。
<!-- 配置DBCP數據源--> <bean id="myDBCPDataSource" class="org.apache.commons.dbcp.BasicDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver"></property><property name="url" value="jdbc:mysql:///test"></property><property name="username" value="root"></property><property name="password" value="02000059"></property> </bean>1.3.3 C3P0數據源ComboPooledDataSource
- 使用C3P0數據源,需要導入一個Jar包,在Spring依賴庫的解壓目錄的com.mchange.c3p0目錄中。
- C3P0數據源是ComboPooledDataSource,Ctrl + 0 查看其類結構可以看到,其有driverClass、jdbcUrl、user、password四個DB連接屬性
<!-- 配置C3P0數據源--> <bean id="myC3P0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"><property name="driverClass" value="com.mysql.jdbc.Driver"></property><property name="jdbcUrl" value="jdbc:mysql:///test"></property><property name="user" value="root"></property><property name="password" value="02000059"></property> </bean>1.4 從屬性文件中讀取數據庫連接信息
- 為了便于維護,可以將數據庫連接信息寫入到屬性文件中,使得Spring配置文件從中讀取數據。
- 屬性文件名稱隨意,但是一般都是放在src下。
- Spring配置文件從屬性文件中讀取數據時,需要在<property/>的value屬性中使用${},將在屬性文件中定義的key括起來,以引用指定屬性的值。 <!-- 配置C3P0數據源--> <bean id="myC3P0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"><property name="driverClass" value="${jdbc.driver}"></property><property name="jdbcUrl" value="${jdbc.url}"></property><property name="user" value="${jdbc.user}"></property><property name="password" value="${jdbc.password}"></property> </bean>
- 該屬性文件若要被Spring配置文件讀取,其必須在配置文件中進行注冊。注冊方式有兩種。
1、<bean/>方式 - 使用class為PropertyPlaceholdConfigurer
- 以PropertyPlaceholdConfigurer類的bean實例的方式進行注冊。該類有一個屬性location,用于指定屬性文件的位置。這種方式不常用。 <!-- 注冊屬性文件方式一 --> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><property name="location" value="classpath:jdbc.properties"></property> </bean>
2、<context:property-placehlder/>方式
- 該方式要求在Spring配置文件頭部加入context的約束,即修改配置文件頭。
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- bean definitions here --> </beans> - <context:property-placeholder/>標簽中有一個屬性location,用于指定屬性文件的位置。 <!-- 注冊屬性文件方式二 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
1.5 配置JDBC模版
- JDBC模版類JdbcTemplate從其父類中JdbcAccessor繼承了一個屬性dataSource,用于接收數據源。
- 查看JdbcTemplate源碼,以及JdbcAccessor的類結構:
<!-- 配置JDBC模版 --> <bean id="myJdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="myC3P0DataSource"></property> </bean>1.6 Dao實現類繼承JdbcDaoSupport類
- JdbcDaoSupport類中有一個屬性JdbcTemplate,用于接收JDBC模版。所以Dao實現類繼承了JdbcDaoSupport類后,也就具有了JDBC模版屬性。在配置文件中,只要將模版對象注入即可。
<!-- 配置JDBC模版 --> <bean id="myJdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="myC3P0DataSource"></property> </bean> <!-- 配置Dao --> <bean id="myDao" class="com.eason.spring4.dao.impl.UserDaoImpl"><property name="jdbcTemplate" ref="myJdbcTemplate"></property> </bean> - 再仔細查看JdbcDaoSupport類,發現其有一個dataSource屬性,查看setDataSource()方法體可知,若Jdbc模版為null,則自動會創建一個模版對象。
- 所以,在Spring配置文件中,對于JDBC模版對象的配置完全可以省去,而是在Dao實現類中直接注入數據源對象。這樣會讓系統自動創建JDBC模版對象。 <!-- 配置Dao -->
<bean id="myDao" class="com.eason.spring4.dao.impl.UserDaoImpl"><property name="dataSource" ref="myC3P0DataSource"></property>
</bean>
1.7 對于DB的增、刪、改操作
- JdbcTemplate類中提供了對DB進行修改、查詢的方法。Dao實現類使用繼承自JdbcDaoSupport的getTemplate()方法,可以獲取到JDBC模版方法。 public interface IUserDao { void insertUser(User user); void deleteUser(int id); void updateUser(User user); String selectUsernameById(int id); List<String> selectUsernamesByAge(int age); User selectUserById(int id); List<User> selectAllUsers(); }
- 對DB的增刪改都是通過update()方法實現的。該方法常用的額重載方法有兩個:
- public int update(String sql);
- public int update(String sql, Object ...args);
- 第一個參數為要執行的sql語句,第2個參數為要執行的sql語句中所包含的動態參數。其返回值為所影響記錄的條數,一般不使用。 public class UserDaoImpl extends JdbcDaoSupport implements IUserDao{
@Override
public void insertUser(User user) {String sql = "insert into t_user(username, age) values(?, ?)";this.getJdbcTemplate().update(sql, user.getUsername(), user.getAge());
}
@Override
public void deleteUser(int id) {String sql = "delete from t_user where id=?";this.getJdbcTemplate().update(sql, id);
}
@Override
public void updateUser(User user) {String sql = "update t_user set username = ?, age = ? where id = ?";this.getJdbcTemplate().update(sql, user.getUsername(), user.getAge(), user.getId());
}
}
1.8 對DB的查詢操作
- JDBC模版的查詢結果均是以對象的形式返回。根據返回對象的不同,可以將查詢分為兩類:簡單對象查詢,與自定義對象查詢。
- 簡單對象查詢,查詢結果為String、Integer等簡單對象類型,或者該類型作為元素的集合的類型,如List<String>等。
- 自定義對象查詢:查詢結果為自定義類型,如User等,或者該類型作為元素的集合類型,如List<User>等。
1.8.1 簡單對象查詢
- 常用的簡單對象查詢方法有:查詢結果為單個對象的queryForObject()與查詢結果為List的queryForList()。
- public T queryForObject(String sql, Class<T> type, Object... args);
- public List<T> queryForList(String sql, Class<T> type, Object ...args); @Override
public String selectUsernameById(int id) {String sql = "select username from t_user where id = ?";String username = this.getJdbcTemplate().queryForObject(sql, String.class, id);return username;
}
@Override
public List<String> selectUsernamesByAge(int age) {String sql = "select username from t_user where age = ?";List<String> allUsernames = this.getJdbcTemplate().queryForList(sql, String.class, age);return allUsernames;
}
1.8.2 自定義對象查詢
- 常用的自定義對象查詢方法有:查詢結果為單個對象的queryForObject()與查詢結果為List的query()。
- public T queryForObject(String sql, RowMapper<T> m, Object ...args)
- public List<T> query(String sql, RowMapper<T> m , Object ...args)
- 注意,RowMapper為記錄映射接口,用于將查詢結果中每一條記錄包裝成指定對象。該接口中有一個方法需要實現。
- public Object mapRow(ResultSet rs, int rowNum):參數rowNum表示總的結果集中當前行的行號,但是參數rs并不表示總的結果集,而是表示rowNum所代表的當前行的記錄所定義的結果集,僅僅是當前行的結果。
- 一般來說,該方法體中就是實現將查詢結果中當前行的數據包裝為一個指定對象。 public class UserRowMapper implements RowMapper<User>{
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {User user = new User();user.setId(rs.getInt("id"));user.setUsername(rs.getString("username"));user.setAge(rs.getInt("age"));return user;
}
} @Override
public User selectUserById(int id) {String sql = "select * from t_user where id = ?";User user = this.getJdbcTemplate().queryForObject(sql, new UserRowMapper(), id);return user;
}
@Override
public List<User> selectAllUsers() {String sql = "select * from t_user";List<User> users = this.getJdbcTemplate().query(sql, new UserRowMapper());return users;
}
1.9 注意:JDBC模版對象時多例的
- JdbcTemplate對象是多例的,即系統會為每一個使用模版對象的線程(方法)創建一個JdbcTemplate實例,并且在該線程(方法)結束時,自動釋放JdbcTemplate實例。所以在每次使用JdbcTemplate對象時,都需要通過getJdbcTemplate()方法獲取。
2 Spring的事務管理
- 事務原來是數據庫中的概念,在Dao層。但是一般情況下,需要將事務提升到業務層,即Service層。這樣做是為了能夠使用事務的特性來管理具體的業務。
- 在Spring中通常可以通過以下三種方式來實現對事務的管理:
1、使用Spring的事務代理工廠管理事務;
2、使用Spring的事務注解管理事務;
3、使用AspectJ的AOP配置管理事務;2.1 Spring事務管理API
- Spring的事務管理,主要用到兩個事務相關的接口。
2.1.1 事務管理器接口
- 事務管理器是PlatformTransactionManager接口對象。其主要用于完成事務的提交、回滾、以及獲取事務的狀態信息。查看SpringAPI幫助文檔:Spring框架解壓目錄下的docs/javadoc-api/index.html。
1、常用的兩個實現類: - PlatformTransactionManager接口有兩個常用的實現類:
- DataSourceTransactionManager:使用JDBC或者iBatis進行持久化數據時使用。
- HibernateTransactionManager:使用Hibernate進行持久化數據時使用。
2、Spring的回滾方式:
- Spring事務的默認回滾方式是:發生運行時異常時回滾,發生受查異常時提交。不過,對于受查異常,程序員也可以手工設置其回滾方式。
3、回顧錯誤和異常:
- Throwable類是Java語言中所有錯誤或者異常的超類。只有當對象時此類(或者其子類之一)的實例時,才能夠通過Java虛擬機或者Java的throw語句拋出。
- Error是程序在運行過程中出現的無法處理的錯誤。比如OutOfMemoryError、ThreadDeath、NoSuchMethodError等。當這些錯誤發生時,程序是無法處理(捕獲或者拋出)的,JVM一般會終止線程。
- 程序在編譯和運行時出現的另一類錯誤稱之為異常,它是JVM通知程序員的一種方式。通過這種方式,讓程序員知道已經或者可能出現錯誤,要求程序員對其進行處理。
- 異常分為運行時異常和受查異常。
- 運行時異常,是RuntimeException類或者其子類,即只有在運行時才能夠出現的異常。如,NullPointerException、ArrayIndexOutOfBoundException、IllegalArgumentException等均屬于運行時異常。這些異常由JVM拋出,在編譯時不要求必須處理(捕獲或者拋出)。但是,只要代碼編寫足夠仔細,程序足夠健壯,運行時異常是可以避免的。
- 注意,Hibernate異常HibernateException就屬于運行時異常。
- 受查異常,也叫作編譯時異常,即在代碼編寫時要求必須捕獲或者拋出的異常。若不處理,則無法通過編譯。如SQLException、ClassNotFoundException、IOException等都屬于受查異常。
- RuntimeException及其子類以外的異常,均屬于受查異常。當然,用戶自定義的Exception的子類,即用戶自定義的異常也屬于受查異常。程序員在定義異常時,只要未明確聲明定義為RuntimeException的子類,那么定義的就是受查異常。
2.1.2 事務定義接口
- 事務定義接口TransactionDefinition中定義了事務描述相關的三類常量:事務隔離級別、事務傳播行為、事務默認超時時限,以及對它們的操作。
1、定義了五個事務隔離級別常量:
- 這些常量均是以ISOLATION_開頭,即形如ISOLATION_XXX。
- DEFAULT:采用DB默認的事務隔離級別。MySQL的默認為REPEATABLE_READ:Oracle默認為READ_COMMITTED。
- READ_UNCOMMITTED:讀未提交。未解決任何并發問題。
- READ_COMMITTED:讀已提交。解決臟讀,存在不可重復讀和幻讀。
- REPEATABLE_READ:可重復讀。解決臟讀、不可重復讀,存在幻讀。
- SERIALIZABLE:串行化。不存在并發問題。
2、定義七個事務傳播行為常量:
- 所謂事務傳播行為是指,處于不同事務中的方法在相互調用時,執行期間事務的維護情況。如,A事務中的方法doSome()調用B事務中的方法doOther(),在調用執行期間事務的維護情況,就稱之為事務傳播行為。事務傳播行為是加在方法上的。
- 事務傳播行為常量都是以PROPAGATION_開頭的,形如PROPAGATION_XXX。
- REQUIRED:指定的方法必須在事務內執行。若當前存在事務,就加入到當前事務中;若當前沒有事務,則創建一個新事務。這種傳播行為是最常用的選擇,也是Spring默認的事務傳播行為。
- 如該傳播行為加在doOther()方法上。若doSome()方法在調用doOther()方法時就是在事務內運行的,則doOther()方法的執行也加入到該事務內執行。若doSome()方法在調用doOther()方法時沒有在事務內執行,則doOther()方法會創建一個事務,并在其中執行。
- SUPPORTS:指定的方法支持當前事務,但若當前沒有事務,也可以以非事務方式執行。
- MANDATORY:指定的方法必須在當前事務內執行,若當前沒有事務,則直接拋出異常。
- REQUIRES_NEW:總是新建一個事務,若當前存在事務,就將當前事務掛起,直到新事務執行完畢。
- NOT_SUPPORTED:指定的方法不能在事務環境下執行,若當前存在事務,就將當前事務掛起。
- NEVER:指定的方法不能在事務環境下執行,若當前存在事務,就直接拋出異常。
- NESTED:指定的方法必須在事務內執行。若當前存在事務,則再嵌套事務內部執行;若當前沒有事務,則新建一個新事務。
3、定義了默認事務超時時限:
- 常量TIMEOUT_DEFAULT定義了事務底層默認的超時時限,以及不支持事務超時時限設置的none值。
- 注意,事務的超時時限起作用的條件比較多,且超時的時間計算點較復雜。所以,該值一般就使用默認值即可。
2.2 程序舉例環境搭建
- 舉例:購買股票----transaction_buystock項目。
- 本例要實現模擬購買股票。存在兩個實體:銀行賬戶Account與股票賬戶Stock。當要購買股票時,需要從Account中扣除相應金額的存款,然后在Stock中增加相應的股票數量。而在這個過程中,可能會拋出一個用戶自定義的異常。異常的拋出,將會使得兩個操作回滾。
- 實現步驟:
2.2.1 創建數據庫表
- 創建兩個數據庫表account、stock。
2.2.2 創建實體類
- 創建實體類Account與Stock public class Account {
private Integer id;
private String aname;
private double balance;
//setter and getter()
@Override
public String toString() {return "Account [id=" + id + ", aname=" + aname + ", balance=" + balance + "]";
}
} public class Stock {
private Integer id;
private String sname;
private int count;
//setter and getter()
@Override
public String toString() {return "Stock [id=" + id + ", sname=" + sname + ", count=" + count + "]";
}
}
2.2.3 定義dao接口
- 定義兩個dao的接口IAccountDao與IStockDao。 public interface IAccountDao {
void insertAccount(String aname, double money);
void updateAccount(String aname, double money);
Account selectAccount(String aname);
} public interface IStockDao {
void insertStock(String sname, int count);
void updateStock(String snaem, int count);
Stock selectStock(String sname);
}
2.2.4 定義dao實現類
- 定義兩個dao接口的實現類AccountDaoImpl與StockDaoImpl,注意,它們要繼承自JdbcDaoSupport public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao{
@Override
public void insertAccount(String aname, double money) {String sql = "insert into t_account(aname, balance) values(?, ?)";this.getJdbcTemplate().update(sql, aname, money);
}
@Override
public void updateAccount(String aname, double money) {String sql = "update t_account set balance = balance - ? where aname = ?";this.getJdbcTemplate().update(sql, money, aname);
}
@Override
public Account selectAccount(String aname) {String sql = "select * from t_account where aname = ?";return this.getJdbcTemplate().queryForObject(sql, Account.class, aname);
}
} public class StockDaoImpl extends JdbcDaoSupport implements IStockDao{
@Override
public void insertStock(String sname, int count) {String sql = "insert into t_stock(sname, count) values(?, ?)";this.getJdbcTemplate().update(sql, sname, count);
}
@Override
public void updateStock(String sname, int count) {String sql = "update t_stock set count = count + ? where sname =?";this.getJdbcTemplate().update(sql, count, sname);
}
@Override
public Stock selectStock(String sname) {String sql = "select * from t_stock where sname = ?";return this.getJdbcTemplate().queryForObject(sql, Stock.class, sname);
}
}
2.2.5 定義異常類
- 定義service層可能會拋出的異常類StockException。 public class StockException extends Exception {
public StockException() {super();
}
public StockException(String message) {super(message);
}
}
2.2.6 定義Service接口
- 定義Service接口IStockProcessService: public interface IStockProcessService {
void openAccount(String aname, double money);
void openStock(String sname, int count);
//aname銀行賬戶花money元購買sname的股票amount支
void buyStock(String aname, String sname, double money, int amount) throws StockException;
Account findAccount(String aname);
Stock findStock(String sname);
}
2.2.7 定義service的實現類
- 定義service層接口的實現類StockProcessServiceImpl: public class StockProcessServiceImpl implements IStockProcessService{
private IAccountDao aDao;
private IStockDao sDao;
// setter()此處省略了
@Override
public void openAccount(String aname, double money) {aDao.insertAccount(aname, money);
}
@Override
public void openStock(String sname, int count) {sDao.insertStock(sname, count);
}
@Override
public void buyStock(String aname, String sname, double money, int amount) throws StockException {aDao.updateAccount(aname, money);if(1 == 1) {throw new StockException();}sDao.updateStock(aname, amount);
}
@Override
public Account findAccount(String aname) {return aDao.selectAccount(aname);
}
@Override
public Stock findStock(String sname) {return sDao.selectStock(sname);
}
}
2.2.8 Spring配置文件中添加約束和配置
-
本例中將使用到Spring中DI、AOP、事務等眾多功能,所以將之前用過的所有約束進行了綜合。綜合后的約束為:(并添加了本例中Spring配置文件內容)
<?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:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsd"><!-- 注冊屬性文件方式 --><context:property-placeholder location="classpath:jdbc.properties"/><!-- 配置C3P0數據源--><bean id="myC3P0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"><property name="driverClass" value="${jdbc.driver}"></property><property name="jdbcUrl" value="${jdbc.url}"></property><property name="user" value="${jdbc.user}"></property><property name="password" value="${jdbc.password}"></property></bean><!-- 配置Dao --><bean id="myaDao" class="com.eason.spring.dao.impl.AccountDaoImpl"><property name="dataSource" ref="myC3P0DataSource"></property></bean><bean id="mysDao" class="com.eason.spring.dao.impl.StockDaoImpl"><property name="dataSource" ref="myC3P0DataSource"></property></bean><!-- 配置Service --><bean id="myService" class="com.eason.spring.service.impl.StockProcessServiceImpl"><property name="aDao" ref="myaDao"></property><property name="sDao" ref="mysDao"></property></bean> </beans>2.2.9 定義測試類
- 定義測試類MyTest,現在就可以在無事務代理的情況下運行。 public class MyTest {private IStockProcessService service;@Beforepublic void setUp() {String recource = "applicationContext.xml";ApplicationContext ac = new ClassPathXmlApplicationContext(recource);service = (IStockProcessService) ac.getBean("myService");} @Testpublic void test01() {service.openAccount("a", 2000);service.openStock("s", 100);}@Testpublic void test02() throws StockException {service.buyStock("a", "s", 500, 100);}
}
2.3 使用Spring的事務代理工廠管理事務
- 該方式是,需要為目標類,即Service的實現類創建事務代理。事務代理使用的類是TransactionProxyFactoryBean,該類需要初始化如下一些屬性:
1、transactionManager:事務管理器;2、target:目標對象,即Service實現類對象;3、transactionAttributes:事務屬性設置。 - 對于XML配置代理方式實現事務管理時,受查異常的回滾方式,程序員可以通過以下方式進行設置:通過“-異常”方式,可使得發生指定的異常時事務回滾;通過“+異常”方式,可使得發生指定的異常時事務提交。
- 該方式的實現步驟為:
2.3.1 復制項目
- 復制transaction_buystock項目,并重命名為transaction_proxy。在此基礎上修改。
2.3.2 導入Jar包
- 這里使用到的Spring的AOP,所以需要引入AOP的兩個jar包:aop聯盟,及Spring對AOP實現的Jar包。
2.3.3 在容器中添加事務管理器DataSourceTransactionManager
- 由于本項目使用的是JDBC模版進行持久化,所以使用DataSourceTransactionManager類作為事務管理器。 <!-- 配置事務管理器 -->
<bean id="myTransactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="myC3P0DataSource"></property>
</bean>
2.3.4 在容器中添加事務代理TransactionProxyFactoryBean
<!-- 配置service的事務切面代理 --> <bean id="myServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"><property name="transactionManager" ref="myTransactionManager"></property><property name="target" ref="myService"></property><property name="transactionAttributes"><props><prop key="open">PROPAGATION_REQUIRED</prop><prop key="find">PROPAGATION_SUPPORTS,readOnly</prop><prop key="buyStock">PROPAGATION_REQUIRED,-StockException</prop></props></property> </bean>2.3.5 修改測試類
- 現在就可以通過事務代理來運行。 public class MyTest {
private IStockProcessService service;
@Before
public void setUp() {String recource = "applicationContext.xml";ApplicationContext ac = new ClassPathXmlApplicationContext(recource);service = (IStockProcessService) ac.getBean("myServiceProxy");
}
@Test
public void test01() {service.openAccount("a", 2000);service.openStock("s", 100);
}
@Test
public void test02() throws StockException {service.buyStock("a", "s", 500, 100);
}
}
2.4 使用Spring的事務注解管理事務
- 通過@Transactional注解方式,也可將事務織入到相應方法中。而使用注解方式,只需要在配置文件中加入一個tx標簽,以告訴spring使用注解來完成事務的織入。該標簽只需要指定一個屬性,事務管理器。 <!-- 開啟事務注解驅動 -->
<tx:annotation-driven transaction-manager="myTransactionManager"/>
@Transactional的所有可選屬性如下所示:
1、propagation:用于設置事務傳播屬性。該屬性類型為Propagation枚舉,默認值為Propagation.REQUIRED。
2、isolation:用于設置事務的隔離級別。該屬性類型為Isolation枚舉,默認值為Isolation.DEFAULT。
3、readOnly:用于設置該方法對數據庫的操作是否是只讀的。該屬性為boolean,默認值為false。
4、timeout:用于設置本操作與數據庫連接的超時時限。單位為秒,類型為int,默認值為-1,即沒有時限。
5、rollbackFor:指定需要回滾的異常類。類型為Class[],默認值為空數組。當然,若只有一個異常類時,可以不使用數組。
6、rollbackForClassName:指定需要回滾的異常類類名。類型為String[],默認值為空數組。當然,若只有一個異常類,可以不使用數組。
7、noRollbackFor:指定不需要回滾的異常類。類型為String[],默認值為空數組。當然,若只有一個異常類,可以不使用數組。
8、noRollbackForClassName:指定不需要回滾的異常類類名。類型為String[],默認值為空數組。當然,若只有一個異常類,可以不使用數組。 - 需要注意的是,@Transactional若用在方法上,只能夠用于public方法上,對于其他非public方法,如果加上了注解@Transactional,雖然Spring不會報錯,但是不會將指定事務織入到該方法中。因為Spring會忽略所有非public方法上的@Transaction注解。
- 若@Transaction注解在類上,則表示該類上所有的方法均將在執行時織入事務。
2.4.1 step1:復制項目
- 復制transaction_buystock項目,并重命名為transaction_annotation,在此基礎上進行修改。
2.4.2 在容器中添加事務管理器
<!-- 配置事務管理器 --> <bean id="myTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="myC3P0DataSource"></property> </bean>2.4.3 在Service實現類方法上添加注解
@Transactional(propagation=Propagation.REQUIRED) @Override public void openStock(String sname, int count) {sDao.insertStock(sname, count); } @Transactional(propagation=Propagation.REQUIRED, rollbackFor=StockException.class) @Override public void buyStock(String aname, String sname, double money, int amount) throws StockException {aDao.updateAccount(aname, money);if(1 == 1) {throw new StockException();}sDao.updateStock(aname, amount); } @Transactional(propagation=Propagation.SUPPORTS, readOnly=true) @Override public Account findAccount(String aname) {return aDao.selectAccount(aname); }2.4.4 添加配置文件內容
<!-- 開啟事務注解驅動 --> <tx:annotation-driven transaction-manager="myTransactionManager"/>2.4.5 修改測試類
- 由于配置文件中已經不存在事務代理對象,所以測試類中要從容器中獲取的將不再是事務代理對象,而是原來的目標對象。 @Before
public void setUp() {String recource = "applicationContext.xml";ApplicationContext ac = new ClassPathXmlApplicationContext(recource);service = (IStockProcessService) ac.getBean("myService");
}
2.5 使用AspectJ的AOP配置管理事務
- 使用XML配置事務代理的方式的不足是,每個目標類都需要配置事務代理。當目標類較多時,配置文件會變得非常臃腫。
- 使用XML配置顧問方式可以自動為每個符合切入點表達式的類生成事務代理。其用法很簡單,只需要將前面代碼中關于事務代理的配置刪除,再替換為如下內容即可。
2.5.1 復制項目
- 復制transaction_buystock項目,并重命名為transaction_advisor。在此基礎上修改。
2.5.2 導入Jar包
- 這里使用Spring的AspectJ方式將事務進行的織入,所以,這里除了前面導入的aop的兩個Jar包外,還需要兩個Jar包:AspectJ的Jar包,以及Spring整合AspectJ的Jar包。
2.5.3 在容器中添加事務管理器
<!-- 配置事務管理器 --> <bean id="myTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="myC3P0DataSource"></property> </bean>2.5.4 配置事務通知
- 為了事務通知設置相關屬性。用于指定要將事務以什么樣的方式織入給哪些方法。
- 例如,應用到buyStock方法上的事務要求是必須的,且當buyStock方法發生StockException后,要回滾。 <!-- 配置事務通知 -->
<tx:advice id="myAdvice" transaction-manager="myTransactionManager"><tx:attributes><tx:method name="open" propagation="REQUIRED"/><tx:method name="find" propagation="SUPPORTS" read-only="true"/><tx:method name="buyStock" propagation="REQUIRED" rollback-for="StockException"/></tx:attributes>
</tx:advice>
2.5.5 配置顧問
- 指定將配置好的事務通知,織入給誰。 <!-- 配置顧問(顧問 = 通知 + 切入點) --> <aop:config><aop:pointcut expression="execution(* com.eason.spring.service.*.*(..))" id="myPointcut"/><aop:advisor advice-ref="myAdvice" pointcut-ref="myPointcut"/> </aop:config>
- 需要注意的是,不能夠寫為下面的形式,切入點表達式一定要指明切入點在Service層,否則將會拋出對數據源的循環引用異常。因為下面的寫法同時會把Service層和Dao層的方法均作為切入點。Service與Dao中均注入了數據源,而Service又調用了Dao,所以就出現了循環調用的異常。
2.5.6 修改測試類
- 測試類中要從容器中獲取的將不再是事務代理對象,而是目標對象。 @Before public void setUp() {String recource = "applicationContext.xml";ApplicationContext ac = new ClassPathXmlApplicationContext(recource);service = (IStockProcessService) ac.getBean("myService"); }
轉載于:https://blog.51cto.com/12402717/2092171
總結
以上是生活随笔為你收集整理的SSH框架之Spring4专题4:Spring与DAO的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: NJ4X源码阅读分析笔记系列(一)——项
- 下一篇: Keras之父:我担心的是AI被社交媒体