本篇隨筆是上兩篇的延續:三種數據庫訪問——原生JDBC;數據庫連接池:Druid
Spring的JDBC框架
Spring主要提供JDBC模板方式、關系數據庫對象化方式、SimpleJdbc方式、事務管理來簡化JDBC編程
Spring提供了3個模板類:
- JdbcTemplate:Spring里最基本的JDBC模板,利用JDBC和簡單的索引參數查詢提供對數據庫的簡單訪問。
- NamedParameterJdbcTemplate:能夠在執行查詢時把值綁定到SQL里的命名參數,而不是使用索引參數。
- SimpleJdbcTemplate:利用Java 5的特性,比如自動裝箱、通用(generic)和可變參數列表來簡化JDBC模板的使用。
JdbcTemplate主要提供以下4類方法:
- execute方法:可以用於執行任何SQL語句,一般用於執行DDL語句;
- update方法及batchUpdate方法:update方法用於執行新增、修改、刪除等語句;batchUpdate方法用於執行批處理相關語句;
- query方法及queryForXXX方法:用於執行查詢相關語句;
- call方法:用於執行存儲過程、函數相關語句。
示例項目
接下來,通過一個示例項目來展示如何使用Spring的JDBC框架訪問數據庫。假設該項目的功能有:保存用戶信息、查詢用戶信息。
1、首先創建數據庫及用戶表:
CREATE TABLE `user` ( `id` int(10) NOT NULL auto_increment, `name` varchar(30) default NULL, `age` int(3) default NULL, PRIMARY KEY (`id`) )
2、創建工程(Maven工程),添加依賴
依賴配置:
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.26</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>0.2.25</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>3.2.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>3.2.4.RELEASE</version> </dependency>
依賴的包有:Junit、mysql驅動器、druid(阿里巴巴開發的高性能的數據庫連接池)、spring-context、spring-jdbc
3、實體類User
public class User implements Serializable{ private Long id; private String name; private Integer age; //setter getter 略 public String toString() { return "User [id=" + id + ", name=" + name + ", age=" + age + "]"; } }
4、Dao接口及實現
package edu.shao.springJdbc.dao; import java.util.List; import edu.shao.springJdbc.po.User; public interface IUserDao { public void save(User user); public List<User> query(String sql,Object[] args); }
package edu.shao.springJdbc.dao.impl; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.support.JdbcDaoSupport; import edu.shao.springJdbc.dao.IUserDao; import edu.shao.springJdbc.po.User; public class UserDaoImpl extends JdbcDaoSupport implements IUserDao { class UserRowMapper implements RowMapper<User> { //實現ResultSet到User實體的轉換 public User mapRow(ResultSet rs, int rowNum) throws SQLException { User m = new User(); m.setId(rs.getLong("id")); m.setName(rs.getString("name")); m.setAge(rs.getInt("age")); return m; } }; public void save(User model) { getJdbcTemplate().update("insert into user(name,age) values(?,?)", model.getName(), model.getAge()); } public List<User> query(String sql, Object[] args) { return getJdbcTemplate().query(sql, args, new UserRowMapper()); } }
5、Service接口及實現:
package edu.shao.springJdbc.service; public interface IUserService { void saveUser(); void saveUserThrowException() throws Exception; void findUsers(); }
package edu.shao.springJdbc.service.impl; import java.util.List; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import edu.shao.springJdbc.dao.IUserDao; import edu.shao.springJdbc.po.User; import edu.shao.springJdbc.service.IUserService; @Transactional public class UserServiceImpl implements IUserService { private IUserDao userDao; public void saveUser() { User u1=new User(); u1.setName("邵"); u1.setAge(24); userDao.save(u1); if(1+1>1){ throw new RuntimeException("Runtime error...");//拋出運行時異常:RuntimeException } User u2=new User(); u2.setName("陳"); u2.setAge(20); userDao.save(u2); } public void saveUserThrowException() throws Exception { User u1=new User(); u1.setName("邵"); u1.setAge(24); userDao.save(u1); if(1+1>1){ throw new Exception("Runtime error...");//拋出一般的異常:Exception } User u2=new User(); u2.setName("陳"); u2.setAge(20); userDao.save(u2); } @Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true) public void findUsers() { List<User> users=userDao.query("select * from user where age>?", new Object[]{17}); for (User user : users) { System.out.println(user); } } //setter getter略 }
Spring對事務的管理有豐富的支持,Spring提供了編程式配置事務和聲明式配置事務,其中,聲明式事務有以下兩種方式 :
- 一種是使用Annotation注解的方式(官方推薦)
- 一種是基於Xml的方式
(編程式的事務處理有些侵入性。通常我們的事務需求並沒有要求在事務的邊界上進行如此精確的控制,我們一般采用"聲明式事務"。)
上面我們采用了基於注解的方式來配置事務。
6、配置數據庫連接池、配置bean的依賴關系、配置事務處理器
applicationContext-dataSource.xml
<?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-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="jdbc:mysql://localhost:3306/test" /> <property name="username" value="root" /> <property name="password" value="123456" /> <property name="initialSize" value="1" /> <property name="maxActive" value="20" /> </bean> </beans>
applicationContext-jdbc.xml
<?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/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <import resource="applicationContext-dataSource.xml" /> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 首先定義抽象的abstractDao,其有一個jdbcTemplate屬性,從而可以讓繼承的子類自動繼承jdbcTemplate屬性注入; --> <bean id="abstractDao" abstract="true"> <property name="jdbcTemplate" ref="jdbcTemplate" /> </bean> <bean id="userDao" class="edu.shao.springJdbc.dao.impl.UserDaoImpl" parent="abstractDao" /> <bean id="userService" class="edu.shao.springJdbc.service.impl.UserServiceImpl"> <property name="userDao" ref="userDao"></property> </bean> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <tx:annotation-driven transaction-manager="txManager" /> </beans>
上面<tx:annotation-driven transaction-manager="txManager" /> 這句話的作用是注冊事務處理器。
我們只需要在類上加上注解@Transactional,就可以指定這個類需要受Spring的事務管理。默認Spring為每個方法開啟一個事務,如果方法發生運行期異常(RuntimeException),事務會進行回滾;如果發生一般的異常(Exception),事務不進行回滾。
7、測試
package edu.shao.springJdbc; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import edu.shao.springJdbc.service.IUserService; public class SpringJdbcTest { private static ApplicationContext ctx = null; @BeforeClass //表示在所以測試方法之前執行,且只執行一次。 public static void onlyOnce() { ctx = new ClassPathXmlApplicationContext("db/applicationContext-jdbc.xml"); } @Test public void testSave(){ IUserService service=ctx.getBean("userService",IUserService.class); service.saveUser(); } @Test public void testSaveThrowException() throws Exception{ IUserService service=ctx.getBean("userService",IUserService.class); service.saveUserThrowException(); } @Test public void testJDBCDaoQuery(){ IUserService service=ctx.getBean("userService",IUserService.class); service.findUsers(); } }
運行測試類,
第一個測試方法,后台輸出異常:java.lang.RuntimeException,查看數據庫發現數據沒有插入,說明事務進行了回滾。
第二個測試方法,后台輸出異常:java.lang.Exception ,查看數據庫發現第一條數據插入,而第二條數據沒有插入,說明事務沒有進行了回滾。
說明了Spring的事務支持默認只對運行期異常(RuntimeException)進行回滾,如果執行sql操作的時候會發生sql異常,不屬於運行期異常,那Spring是怎么進行事務回滾的呢 ?
Spring把SQLException等異常轉化為了DataAccessException,后者是一種RuntimeException,所以只對RuntimeException異常進行回滾是很合理的。
其他注解方式:
- 修改Spring的默認配置,當發生RuntimeException時,可以不讓它進行事務回滾 ,只需要加上一個@Transactional(noRollbackFor=RuntimeException.class)
- 配置對Exception進行回滾,在方法上添加@Transactional(rollbackFor=Exception.class)
- 對於一些查詢工作,因為不需要配置事務支持,我們給其添加注解: @Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)。readOnly=true表示事務中不允許存在更新操作.
關於事務的傳播屬性有下面幾種配置:
- REQUIRED:業務方法需要在一個事務中運行,如果方法運行時,已經處於一個事務中,那么加入到該事務中。否則自己創建一個新的事務。(Spring默認的事務傳播屬性)
- NOT_SUPPORTED:聲明方法不需要事務,如果方法沒有關聯到一個事務,容器不會為它開啟事務。如果方法在一個事務中被調用,該事務被掛起,在方法調用結束后,原先的事務便會恢復執行
- REQUIRESNEW:不管是否存在事務,業務方法總會為自己發起一個新的事務。如果方法運行時已經存在一個事務,則該事務會被掛起,新的事務被創建,直到方法執行結束,新事務才結束,原先的事務才恢復執行.
- MANDATORY:指定業務方法只能在一個已經存在的事務中執行,業務方法不能自己發起事務,如果業務方法沒有在事務的環境下調用,則容器會拋出異常
- SUPPORTS:如果業務方法在事務中被調用,則成為事務中的一部分,如果沒有在事務中調用,則在沒有事務的環境下執行
- NEVER:指定業務方法絕對不能在事務范圍內運行,否則會拋出異常.
- NESTED:如果業務方法運行時已經存在一個事務,則新建一個嵌套的事務,該事務可以有多個回滾點,如果沒有事務,則按REQUIRED屬性執行。 注意:業務方法內部事務的回滾不會對外部事務造成影響,但是外部事務的回滾會影響內部事務
總結:
事務是企業應用開發的重要組成部分,它使軟件更加可靠。它們確保一種要么全有 要么全無的行為,防止數據不一致而導致的不可預測的錯誤發生。 事務同時也支持並發,防止並發應用線程在操作同一數據時互相影響。
以前我們寫Jdbc代碼的時候,可能需要自己手動去開啟事務,然后方法執行結束之后再去提交事務,全部都嵌套在我們的業務代碼之中,具有很強的侵入性....
使用Spring提供事務管理機制,我們只需要配置XML或使用Annotion進行注解就可以實現事務的管理和配置,減少了代碼之間的耦合,配置也很方便,很大程度上提升了我們的開發效率。