javascript
SSH框架之Spring4专题4:Spring与DAO
- 本專(zhuān)題內(nèi)容主要包含兩部分:Spring所使用的操作數(shù)據(jù)庫(kù)的技術(shù)之一,JDBC模版的使用;另一部分則為Spring對(duì)于事務(wù)的管理。
- Spring與Dao部分,是Spring的兩大核心技術(shù)loC與AOP的經(jīng)典應(yīng)用體現(xiàn):
- 對(duì)于JDBC模版的使用,是loC的應(yīng)用,是將JDBC模版對(duì)象注入給Dao層的實(shí)現(xiàn)類(lèi)。
- 對(duì)于Spring的事務(wù)管理,是AOP的應(yīng)用,將事務(wù)作為切面織入到Service層的業(yè)務(wù)方法中。
1 Spring與JDBC模版
- 為了避免直接使用JDBC而帶來(lái)的復(fù)雜且亢長(zhǎng)的代碼,Spring提供了一個(gè)強(qiáng)有力的模版類(lèi)--JdbcTemplate來(lái)簡(jiǎn)化JDBC操作。并且,數(shù)據(jù)源DataSource對(duì)象和模版JdbcTemplate對(duì)象均可通過(guò)Bean的形式定義在配置文件中,充分發(fā)揮了依賴(lài)注入的威力。
1.1 導(dǎo)入Jar包
1、Spring的JDBC Jar包:
2、Spring的事務(wù)Jar包:1.2 搭建測(cè)試環(huán)境
1、定義實(shí)體類(lèi)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、定義數(shù)據(jù)庫(kù)以及表:
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:
- 這里僅僅定義一個(gè)UserDaoImpl類(lèi)實(shí)現(xiàn)了IUserDao接口,但不具體寫(xiě)每個(gè)方法的方法實(shí)現(xiàn)。保持默認(rèn)即可。后面會(huì)逐個(gè)通過(guò)Jdbc模版來(lái)實(shí)現(xiàn)。 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、定義測(cè)試類(lèi)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 數(shù)據(jù)源的配置
- 使用JDBC模版,首先需要配飾號(hào)數(shù)據(jù)源,數(shù)據(jù)源直接以Bean的形式配置在Spring配置文件中。根據(jù)數(shù)據(jù)源的不同,其配置方式也不同。下面主要講解三種常用數(shù)據(jù)源的配置方式:
1、Spring默認(rèn)的數(shù)據(jù)源;
2、DBCP數(shù)據(jù)源;
3、C3P0數(shù)據(jù)源;1.3.1 Spring默認(rèn)的數(shù)據(jù)源DriverManagerDataSource
- Spring默認(rèn)的數(shù)據(jù)源為DriverManagerDataSource,其有一個(gè)屬性DriverClassName,用于接收DB驅(qū)動(dòng)。
- Ctrl + O查看類(lèi)結(jié)構(gòu)以及源碼:
- DriverManagerDataSource類(lèi)繼承自AbstractDriverBasedDataSource。其有三個(gè)屬性用于接收連接數(shù)據(jù)庫(kù)的URL、用戶(hù)名和密碼。
- Ctrl + O查看父類(lèi)的類(lèi)結(jié)構(gòu)和源碼:
<!-- 配置Spring默認(rèn)數(shù)據(jù)源 --> <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數(shù)據(jù)源BasicDataSource
- DBCP,DataBase Connection Pool,是apache下的項(xiàng)目,使用該數(shù)據(jù)源,需要導(dǎo)入兩個(gè)Jar包。它們?cè)赟pring依賴(lài)庫(kù)的解壓目錄的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數(shù)據(jù)源是BasicDataSource,Ctrl + O查看其類(lèi)結(jié)構(gòu)可以看到,其有driverClassName、url、username、password四個(gè)DB連接屬性。
<!-- 配置DBCP數(shù)據(jù)源--> <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數(shù)據(jù)源ComboPooledDataSource
- 使用C3P0數(shù)據(jù)源,需要導(dǎo)入一個(gè)Jar包,在Spring依賴(lài)庫(kù)的解壓目錄的com.mchange.c3p0目錄中。
- C3P0數(shù)據(jù)源是ComboPooledDataSource,Ctrl + 0 查看其類(lèi)結(jié)構(gòu)可以看到,其有driverClass、jdbcUrl、user、password四個(gè)DB連接屬性
<!-- 配置C3P0數(shù)據(jù)源--> <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 從屬性文件中讀取數(shù)據(jù)庫(kù)連接信息
- 為了便于維護(hù),可以將數(shù)據(jù)庫(kù)連接信息寫(xiě)入到屬性文件中,使得Spring配置文件從中讀取數(shù)據(jù)。
- 屬性文件名稱(chēng)隨意,但是一般都是放在src下。
- Spring配置文件從屬性文件中讀取數(shù)據(jù)時(shí),需要在<property/>的value屬性中使用${},將在屬性文件中定義的key括起來(lái),以引用指定屬性的值。 <!-- 配置C3P0數(shù)據(jù)源--> <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配置文件讀取,其必須在配置文件中進(jìn)行注冊(cè)。注冊(cè)方式有兩種。
1、<bean/>方式 - 使用class為PropertyPlaceholdConfigurer
- 以PropertyPlaceholdConfigurer類(lèi)的bean實(shí)例的方式進(jìn)行注冊(cè)。該類(lèi)有一個(gè)屬性location,用于指定屬性文件的位置。這種方式不常用。 <!-- 注冊(cè)屬性文件方式一 --> <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/>標(biāo)簽中有一個(gè)屬性location,用于指定屬性文件的位置。 <!-- 注冊(cè)屬性文件方式二 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
1.5 配置JDBC模版
- JDBC模版類(lèi)JdbcTemplate從其父類(lèi)中JdbcAccessor繼承了一個(gè)屬性dataSource,用于接收數(shù)據(jù)源。
- 查看JdbcTemplate源碼,以及JdbcAccessor的類(lèi)結(jié)構(gòu):
<!-- 配置JDBC模版 --> <bean id="myJdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="myC3P0DataSource"></property> </bean>1.6 Dao實(shí)現(xiàn)類(lèi)繼承JdbcDaoSupport類(lèi)
- JdbcDaoSupport類(lèi)中有一個(gè)屬性JdbcTemplate,用于接收J(rèn)DBC模版。所以Dao實(shí)現(xiàn)類(lèi)繼承了JdbcDaoSupport類(lèi)后,也就具有了JDBC模版屬性。在配置文件中,只要將模版對(duì)象注入即可。
<!-- 配置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> - 再仔細(xì)查看JdbcDaoSupport類(lèi),發(fā)現(xiàn)其有一個(gè)dataSource屬性,查看setDataSource()方法體可知,若Jdbc模版為null,則自動(dòng)會(huì)創(chuàng)建一個(gè)模版對(duì)象。
- 所以,在Spring配置文件中,對(duì)于JDBC模版對(duì)象的配置完全可以省去,而是在Dao實(shí)現(xiàn)類(lèi)中直接注入數(shù)據(jù)源對(duì)象。這樣會(huì)讓系統(tǒng)自動(dòng)創(chuàng)建JDBC模版對(duì)象。 <!-- 配置Dao -->
<bean id="myDao" class="com.eason.spring4.dao.impl.UserDaoImpl"><property name="dataSource" ref="myC3P0DataSource"></property>
</bean>
1.7 對(duì)于DB的增、刪、改操作
- JdbcTemplate類(lèi)中提供了對(duì)DB進(jìn)行修改、查詢(xún)的方法。Dao實(shí)現(xiàn)類(lèi)使用繼承自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(); }
- 對(duì)DB的增刪改都是通過(guò)update()方法實(shí)現(xiàn)的。該方法常用的額重載方法有兩個(gè):
- public int update(String sql);
- public int update(String sql, Object ...args);
- 第一個(gè)參數(shù)為要執(zhí)行的sql語(yǔ)句,第2個(gè)參數(shù)為要執(zhí)行的sql語(yǔ)句中所包含的動(dòng)態(tài)參數(shù)。其返回值為所影響記錄的條數(shù),一般不使用。 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 對(duì)DB的查詢(xún)操作
- JDBC模版的查詢(xún)結(jié)果均是以對(duì)象的形式返回。根據(jù)返回對(duì)象的不同,可以將查詢(xún)分為兩類(lèi):簡(jiǎn)單對(duì)象查詢(xún),與自定義對(duì)象查詢(xún)。
- 簡(jiǎn)單對(duì)象查詢(xún),查詢(xún)結(jié)果為String、Integer等簡(jiǎn)單對(duì)象類(lèi)型,或者該類(lèi)型作為元素的集合的類(lèi)型,如List<String>等。
- 自定義對(duì)象查詢(xún):查詢(xún)結(jié)果為自定義類(lèi)型,如User等,或者該類(lèi)型作為元素的集合類(lèi)型,如List<User>等。
1.8.1 簡(jiǎn)單對(duì)象查詢(xún)
- 常用的簡(jiǎn)單對(duì)象查詢(xún)方法有:查詢(xún)結(jié)果為單個(gè)對(duì)象的queryForObject()與查詢(xún)結(jié)果為L(zhǎng)ist的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 自定義對(duì)象查詢(xún)
- 常用的自定義對(duì)象查詢(xún)方法有:查詢(xún)結(jié)果為單個(gè)對(duì)象的queryForObject()與查詢(xún)結(jié)果為L(zhǎng)ist的query()。
- public T queryForObject(String sql, RowMapper<T> m, Object ...args)
- public List<T> query(String sql, RowMapper<T> m , Object ...args)
- 注意,RowMapper為記錄映射接口,用于將查詢(xún)結(jié)果中每一條記錄包裝成指定對(duì)象。該接口中有一個(gè)方法需要實(shí)現(xiàn)。
- public Object mapRow(ResultSet rs, int rowNum):參數(shù)rowNum表示總的結(jié)果集中當(dāng)前行的行號(hào),但是參數(shù)rs并不表示總的結(jié)果集,而是表示rowNum所代表的當(dāng)前行的記錄所定義的結(jié)果集,僅僅是當(dāng)前行的結(jié)果。
- 一般來(lái)說(shuō),該方法體中就是實(shí)現(xiàn)將查詢(xún)結(jié)果中當(dāng)前行的數(shù)據(jù)包裝為一個(gè)指定對(duì)象。 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模版對(duì)象時(shí)多例的
- JdbcTemplate對(duì)象是多例的,即系統(tǒng)會(huì)為每一個(gè)使用模版對(duì)象的線程(方法)創(chuàng)建一個(gè)JdbcTemplate實(shí)例,并且在該線程(方法)結(jié)束時(shí),自動(dòng)釋放JdbcTemplate實(shí)例。所以在每次使用JdbcTemplate對(duì)象時(shí),都需要通過(guò)getJdbcTemplate()方法獲取。
2 Spring的事務(wù)管理
- 事務(wù)原來(lái)是數(shù)據(jù)庫(kù)中的概念,在Dao層。但是一般情況下,需要將事務(wù)提升到業(yè)務(wù)層,即Service層。這樣做是為了能夠使用事務(wù)的特性來(lái)管理具體的業(yè)務(wù)。
- 在Spring中通常可以通過(guò)以下三種方式來(lái)實(shí)現(xiàn)對(duì)事務(wù)的管理:
1、使用Spring的事務(wù)代理工廠管理事務(wù);
2、使用Spring的事務(wù)注解管理事務(wù);
3、使用AspectJ的AOP配置管理事務(wù);2.1 Spring事務(wù)管理API
- Spring的事務(wù)管理,主要用到兩個(gè)事務(wù)相關(guān)的接口。
2.1.1 事務(wù)管理器接口
- 事務(wù)管理器是PlatformTransactionManager接口對(duì)象。其主要用于完成事務(wù)的提交、回滾、以及獲取事務(wù)的狀態(tài)信息。查看SpringAPI幫助文檔:Spring框架解壓目錄下的docs/javadoc-api/index.html。
1、常用的兩個(gè)實(shí)現(xiàn)類(lèi): - PlatformTransactionManager接口有兩個(gè)常用的實(shí)現(xiàn)類(lèi):
- DataSourceTransactionManager:使用JDBC或者iBatis進(jìn)行持久化數(shù)據(jù)時(shí)使用。
- HibernateTransactionManager:使用Hibernate進(jìn)行持久化數(shù)據(jù)時(shí)使用。
2、Spring的回滾方式:
- Spring事務(wù)的默認(rèn)回滾方式是:發(fā)生運(yùn)行時(shí)異常時(shí)回滾,發(fā)生受查異常時(shí)提交。不過(guò),對(duì)于受查異常,程序員也可以手工設(shè)置其回滾方式。
3、回顧錯(cuò)誤和異常:
- Throwable類(lèi)是Java語(yǔ)言中所有錯(cuò)誤或者異常的超類(lèi)。只有當(dāng)對(duì)象時(shí)此類(lèi)(或者其子類(lèi)之一)的實(shí)例時(shí),才能夠通過(guò)Java虛擬機(jī)或者Java的throw語(yǔ)句拋出。
- Error是程序在運(yùn)行過(guò)程中出現(xiàn)的無(wú)法處理的錯(cuò)誤。比如OutOfMemoryError、ThreadDeath、NoSuchMethodError等。當(dāng)這些錯(cuò)誤發(fā)生時(shí),程序是無(wú)法處理(捕獲或者拋出)的,JVM一般會(huì)終止線程。
- 程序在編譯和運(yùn)行時(shí)出現(xiàn)的另一類(lèi)錯(cuò)誤稱(chēng)之為異常,它是JVM通知程序員的一種方式。通過(guò)這種方式,讓程序員知道已經(jīng)或者可能出現(xiàn)錯(cuò)誤,要求程序員對(duì)其進(jìn)行處理。
- 異常分為運(yùn)行時(shí)異常和受查異常。
- 運(yùn)行時(shí)異常,是RuntimeException類(lèi)或者其子類(lèi),即只有在運(yùn)行時(shí)才能夠出現(xiàn)的異常。如,NullPointerException、ArrayIndexOutOfBoundException、IllegalArgumentException等均屬于運(yùn)行時(shí)異常。這些異常由JVM拋出,在編譯時(shí)不要求必須處理(捕獲或者拋出)。但是,只要代碼編寫(xiě)足夠仔細(xì),程序足夠健壯,運(yùn)行時(shí)異常是可以避免的。
- 注意,Hibernate異常HibernateException就屬于運(yùn)行時(shí)異常。
- 受查異常,也叫作編譯時(shí)異常,即在代碼編寫(xiě)時(shí)要求必須捕獲或者拋出的異常。若不處理,則無(wú)法通過(guò)編譯。如SQLException、ClassNotFoundException、IOException等都屬于受查異常。
- RuntimeException及其子類(lèi)以外的異常,均屬于受查異常。當(dāng)然,用戶(hù)自定義的Exception的子類(lèi),即用戶(hù)自定義的異常也屬于受查異常。程序員在定義異常時(shí),只要未明確聲明定義為RuntimeException的子類(lèi),那么定義的就是受查異常。
2.1.2 事務(wù)定義接口
- 事務(wù)定義接口TransactionDefinition中定義了事務(wù)描述相關(guān)的三類(lèi)常量:事務(wù)隔離級(jí)別、事務(wù)傳播行為、事務(wù)默認(rèn)超時(shí)時(shí)限,以及對(duì)它們的操作。
1、定義了五個(gè)事務(wù)隔離級(jí)別常量:
- 這些常量均是以ISOLATION_開(kāi)頭,即形如ISOLATION_XXX。
- DEFAULT:采用DB默認(rèn)的事務(wù)隔離級(jí)別。MySQL的默認(rèn)為REPEATABLE_READ:Oracle默認(rèn)為READ_COMMITTED。
- READ_UNCOMMITTED:讀未提交。未解決任何并發(fā)問(wèn)題。
- READ_COMMITTED:讀已提交。解決臟讀,存在不可重復(fù)讀和幻讀。
- REPEATABLE_READ:可重復(fù)讀。解決臟讀、不可重復(fù)讀,存在幻讀。
- SERIALIZABLE:串行化。不存在并發(fā)問(wèn)題。
2、定義七個(gè)事務(wù)傳播行為常量:
- 所謂事務(wù)傳播行為是指,處于不同事務(wù)中的方法在相互調(diào)用時(shí),執(zhí)行期間事務(wù)的維護(hù)情況。如,A事務(wù)中的方法doSome()調(diào)用B事務(wù)中的方法doOther(),在調(diào)用執(zhí)行期間事務(wù)的維護(hù)情況,就稱(chēng)之為事務(wù)傳播行為。事務(wù)傳播行為是加在方法上的。
- 事務(wù)傳播行為常量都是以PROPAGATION_開(kāi)頭的,形如PROPAGATION_XXX。
- REQUIRED:指定的方法必須在事務(wù)內(nèi)執(zhí)行。若當(dāng)前存在事務(wù),就加入到當(dāng)前事務(wù)中;若當(dāng)前沒(méi)有事務(wù),則創(chuàng)建一個(gè)新事務(wù)。這種傳播行為是最常用的選擇,也是Spring默認(rèn)的事務(wù)傳播行為。
- 如該傳播行為加在doOther()方法上。若doSome()方法在調(diào)用doOther()方法時(shí)就是在事務(wù)內(nèi)運(yùn)行的,則doOther()方法的執(zhí)行也加入到該事務(wù)內(nèi)執(zhí)行。若doSome()方法在調(diào)用doOther()方法時(shí)沒(méi)有在事務(wù)內(nèi)執(zhí)行,則doOther()方法會(huì)創(chuàng)建一個(gè)事務(wù),并在其中執(zhí)行。
- SUPPORTS:指定的方法支持當(dāng)前事務(wù),但若當(dāng)前沒(méi)有事務(wù),也可以以非事務(wù)方式執(zhí)行。
- MANDATORY:指定的方法必須在當(dāng)前事務(wù)內(nèi)執(zhí)行,若當(dāng)前沒(méi)有事務(wù),則直接拋出異常。
- REQUIRES_NEW:總是新建一個(gè)事務(wù),若當(dāng)前存在事務(wù),就將當(dāng)前事務(wù)掛起,直到新事務(wù)執(zhí)行完畢。
- NOT_SUPPORTED:指定的方法不能在事務(wù)環(huán)境下執(zhí)行,若當(dāng)前存在事務(wù),就將當(dāng)前事務(wù)掛起。
- NEVER:指定的方法不能在事務(wù)環(huán)境下執(zhí)行,若當(dāng)前存在事務(wù),就直接拋出異常。
- NESTED:指定的方法必須在事務(wù)內(nèi)執(zhí)行。若當(dāng)前存在事務(wù),則再嵌套事務(wù)內(nèi)部執(zhí)行;若當(dāng)前沒(méi)有事務(wù),則新建一個(gè)新事務(wù)。
3、定義了默認(rèn)事務(wù)超時(shí)時(shí)限:
- 常量TIMEOUT_DEFAULT定義了事務(wù)底層默認(rèn)的超時(shí)時(shí)限,以及不支持事務(wù)超時(shí)時(shí)限設(shè)置的none值。
- 注意,事務(wù)的超時(shí)時(shí)限起作用的條件比較多,且超時(shí)的時(shí)間計(jì)算點(diǎn)較復(fù)雜。所以,該值一般就使用默認(rèn)值即可。
2.2 程序舉例環(huán)境搭建
- 舉例:購(gòu)買(mǎi)股票----transaction_buystock項(xiàng)目。
- 本例要實(shí)現(xiàn)模擬購(gòu)買(mǎi)股票。存在兩個(gè)實(shí)體:銀行賬戶(hù)Account與股票賬戶(hù)Stock。當(dāng)要購(gòu)買(mǎi)股票時(shí),需要從Account中扣除相應(yīng)金額的存款,然后在Stock中增加相應(yīng)的股票數(shù)量。而在這個(gè)過(guò)程中,可能會(huì)拋出一個(gè)用戶(hù)自定義的異常。異常的拋出,將會(huì)使得兩個(gè)操作回滾。
- 實(shí)現(xiàn)步驟:
2.2.1 創(chuàng)建數(shù)據(jù)庫(kù)表
- 創(chuàng)建兩個(gè)數(shù)據(jù)庫(kù)表account、stock。
2.2.2 創(chuàng)建實(shí)體類(lèi)
- 創(chuàng)建實(shí)體類(lèi)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接口
- 定義兩個(gè)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實(shí)現(xiàn)類(lèi)
- 定義兩個(gè)dao接口的實(shí)現(xiàn)類(lèi)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 定義異常類(lèi)
- 定義service層可能會(huì)拋出的異常類(lèi)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銀行賬戶(hù)花money元購(gòu)買(mǎi)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的實(shí)現(xiàn)類(lèi)
- 定義service層接口的實(shí)現(xiàn)類(lèi)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、事務(wù)等眾多功能,所以將之前用過(guò)的所有約束進(jìn)行了綜合。綜合后的約束為:(并添加了本例中Spring配置文件內(nèi)容)
<?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"><!-- 注冊(cè)屬性文件方式 --><context:property-placeholder location="classpath:jdbc.properties"/><!-- 配置C3P0數(shù)據(jù)源--><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 定義測(cè)試類(lèi)
- 定義測(cè)試類(lèi)MyTest,現(xiàn)在就可以在無(wú)事務(wù)代理的情況下運(yùn)行。 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的事務(wù)代理工廠管理事務(wù)
- 該方式是,需要為目標(biāo)類(lèi),即Service的實(shí)現(xiàn)類(lèi)創(chuàng)建事務(wù)代理。事務(wù)代理使用的類(lèi)是TransactionProxyFactoryBean,該類(lèi)需要初始化如下一些屬性:
1、transactionManager:事務(wù)管理器;2、target:目標(biāo)對(duì)象,即Service實(shí)現(xiàn)類(lèi)對(duì)象;3、transactionAttributes:事務(wù)屬性設(shè)置。 - 對(duì)于XML配置代理方式實(shí)現(xiàn)事務(wù)管理時(shí),受查異常的回滾方式,程序員可以通過(guò)以下方式進(jìn)行設(shè)置:通過(guò)“-異?!狈绞?#xff0c;可使得發(fā)生指定的異常時(shí)事務(wù)回滾;通過(guò)“+異常”方式,可使得發(fā)生指定的異常時(shí)事務(wù)提交。
- 該方式的實(shí)現(xiàn)步驟為:
2.3.1 復(fù)制項(xiàng)目
- 復(fù)制transaction_buystock項(xiàng)目,并重命名為transaction_proxy。在此基礎(chǔ)上修改。
2.3.2 導(dǎo)入Jar包
- 這里使用到的Spring的AOP,所以需要引入AOP的兩個(gè)jar包:aop聯(lián)盟,及Spring對(duì)AOP實(shí)現(xiàn)的Jar包。
2.3.3 在容器中添加事務(wù)管理器DataSourceTransactionManager
- 由于本項(xiàng)目使用的是JDBC模版進(jìn)行持久化,所以使用DataSourceTransactionManager類(lèi)作為事務(wù)管理器。 <!-- 配置事務(wù)管理器 -->
<bean id="myTransactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="myC3P0DataSource"></property>
</bean>
2.3.4 在容器中添加事務(wù)代理TransactionProxyFactoryBean
<!-- 配置service的事務(wù)切面代理 --> <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 修改測(cè)試類(lèi)
- 現(xiàn)在就可以通過(guò)事務(wù)代理來(lái)運(yùn)行。 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的事務(wù)注解管理事務(wù)
- 通過(guò)@Transactional注解方式,也可將事務(wù)織入到相應(yīng)方法中。而使用注解方式,只需要在配置文件中加入一個(gè)tx標(biāo)簽,以告訴spring使用注解來(lái)完成事務(wù)的織入。該標(biāo)簽只需要指定一個(gè)屬性,事務(wù)管理器。 <!-- 開(kāi)啟事務(wù)注解驅(qū)動(dòng) -->
<tx:annotation-driven transaction-manager="myTransactionManager"/>
@Transactional的所有可選屬性如下所示:
1、propagation:用于設(shè)置事務(wù)傳播屬性。該屬性類(lèi)型為Propagation枚舉,默認(rèn)值為Propagation.REQUIRED。
2、isolation:用于設(shè)置事務(wù)的隔離級(jí)別。該屬性類(lèi)型為Isolation枚舉,默認(rèn)值為Isolation.DEFAULT。
3、readOnly:用于設(shè)置該方法對(duì)數(shù)據(jù)庫(kù)的操作是否是只讀的。該屬性為boolean,默認(rèn)值為false。
4、timeout:用于設(shè)置本操作與數(shù)據(jù)庫(kù)連接的超時(shí)時(shí)限。單位為秒,類(lèi)型為int,默認(rèn)值為-1,即沒(méi)有時(shí)限。
5、rollbackFor:指定需要回滾的異常類(lèi)。類(lèi)型為Class[],默認(rèn)值為空數(shù)組。當(dāng)然,若只有一個(gè)異常類(lèi)時(shí),可以不使用數(shù)組。
6、rollbackForClassName:指定需要回滾的異常類(lèi)類(lèi)名。類(lèi)型為String[],默認(rèn)值為空數(shù)組。當(dāng)然,若只有一個(gè)異常類(lèi),可以不使用數(shù)組。
7、noRollbackFor:指定不需要回滾的異常類(lèi)。類(lèi)型為String[],默認(rèn)值為空數(shù)組。當(dāng)然,若只有一個(gè)異常類(lèi),可以不使用數(shù)組。
8、noRollbackForClassName:指定不需要回滾的異常類(lèi)類(lèi)名。類(lèi)型為String[],默認(rèn)值為空數(shù)組。當(dāng)然,若只有一個(gè)異常類(lèi),可以不使用數(shù)組。 - 需要注意的是,@Transactional若用在方法上,只能夠用于public方法上,對(duì)于其他非public方法,如果加上了注解@Transactional,雖然Spring不會(huì)報(bào)錯(cuò),但是不會(huì)將指定事務(wù)織入到該方法中。因?yàn)镾pring會(huì)忽略所有非public方法上的@Transaction注解。
- 若@Transaction注解在類(lèi)上,則表示該類(lèi)上所有的方法均將在執(zhí)行時(shí)織入事務(wù)。
2.4.1 step1:復(fù)制項(xiàng)目
- 復(fù)制transaction_buystock項(xiàng)目,并重命名為transaction_annotation,在此基礎(chǔ)上進(jìn)行修改。
2.4.2 在容器中添加事務(wù)管理器
<!-- 配置事務(wù)管理器 --> <bean id="myTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="myC3P0DataSource"></property> </bean>2.4.3 在Service實(shí)現(xiàn)類(lèi)方法上添加注解
@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 添加配置文件內(nèi)容
<!-- 開(kāi)啟事務(wù)注解驅(qū)動(dòng) --> <tx:annotation-driven transaction-manager="myTransactionManager"/>2.4.5 修改測(cè)試類(lèi)
- 由于配置文件中已經(jīng)不存在事務(wù)代理對(duì)象,所以測(cè)試類(lèi)中要從容器中獲取的將不再是事務(wù)代理對(duì)象,而是原來(lái)的目標(biāo)對(duì)象。 @Before
public void setUp() {String recource = "applicationContext.xml";ApplicationContext ac = new ClassPathXmlApplicationContext(recource);service = (IStockProcessService) ac.getBean("myService");
}
2.5 使用AspectJ的AOP配置管理事務(wù)
- 使用XML配置事務(wù)代理的方式的不足是,每個(gè)目標(biāo)類(lèi)都需要配置事務(wù)代理。當(dāng)目標(biāo)類(lèi)較多時(shí),配置文件會(huì)變得非常臃腫。
- 使用XML配置顧問(wèn)方式可以自動(dòng)為每個(gè)符合切入點(diǎn)表達(dá)式的類(lèi)生成事務(wù)代理。其用法很簡(jiǎn)單,只需要將前面代碼中關(guān)于事務(wù)代理的配置刪除,再替換為如下內(nèi)容即可。
2.5.1 復(fù)制項(xiàng)目
- 復(fù)制transaction_buystock項(xiàng)目,并重命名為transaction_advisor。在此基礎(chǔ)上修改。
2.5.2 導(dǎo)入Jar包
- 這里使用Spring的AspectJ方式將事務(wù)進(jìn)行的織入,所以,這里除了前面導(dǎo)入的aop的兩個(gè)Jar包外,還需要兩個(gè)Jar包:AspectJ的Jar包,以及Spring整合AspectJ的Jar包。
2.5.3 在容器中添加事務(wù)管理器
<!-- 配置事務(wù)管理器 --> <bean id="myTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="myC3P0DataSource"></property> </bean>2.5.4 配置事務(wù)通知
- 為了事務(wù)通知設(shè)置相關(guān)屬性。用于指定要將事務(wù)以什么樣的方式織入給哪些方法。
- 例如,應(yīng)用到buyStock方法上的事務(wù)要求是必須的,且當(dāng)buyStock方法發(fā)生StockException后,要回滾。 <!-- 配置事務(wù)通知 -->
<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 配置顧問(wèn)
- 指定將配置好的事務(wù)通知,織入給誰(shuí)。 <!-- 配置顧問(wèn)(顧問(wèn) = 通知 + 切入點(diǎn)) --> <aop:config><aop:pointcut expression="execution(* com.eason.spring.service.*.*(..))" id="myPointcut"/><aop:advisor advice-ref="myAdvice" pointcut-ref="myPointcut"/> </aop:config>
- 需要注意的是,不能夠?qū)憺橄旅娴男问?#xff0c;切入點(diǎn)表達(dá)式一定要指明切入點(diǎn)在Service層,否則將會(huì)拋出對(duì)數(shù)據(jù)源的循環(huán)引用異常。因?yàn)橄旅娴膶?xiě)法同時(shí)會(huì)把Service層和Dao層的方法均作為切入點(diǎn)。Service與Dao中均注入了數(shù)據(jù)源,而Service又調(diào)用了Dao,所以就出現(xiàn)了循環(huán)調(diào)用的異常。
2.5.6 修改測(cè)試類(lèi)
- 測(cè)試類(lèi)中要從容器中獲取的將不再是事務(wù)代理對(duì)象,而是目標(biāo)對(duì)象。 @Before public void setUp() {String recource = "applicationContext.xml";ApplicationContext ac = new ClassPathXmlApplicationContext(recource);service = (IStockProcessService) ac.getBean("myService"); }
轉(zhuǎn)載于:https://blog.51cto.com/12402717/2092171
總結(jié)
以上是生活随笔為你收集整理的SSH框架之Spring4专题4:Spring与DAO的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: NJ4X源码阅读分析笔记系列(一)——项
- 下一篇: Spring MVC之异常处理