Spring(四)-- JdbcTemplate、聲明式事務
阿新 • • 發佈:2017-08-31
子類 xmla 文件中 epo style 如果 2.2.0 not 可變參 1.Spring提供的一個操作數據庫的技術JdbcTemplate,是對Jdbc的封裝。語法風格非常接近DBUtils。
JdbcTemplate可以直接操作數據庫,加快效率,而且學這個JdbcTemplate也是為聲明式事務做準備,畢竟要對數據庫中的數據進行操縱!
JdbcTemplate中並沒有提供一級緩存,以及類與類之間的關聯關系!就像是spring提供的一個DBUtils。
Spring對數據庫的操作使用JdbcTemplate來封裝JDBC,結合Spring的註入特性可以很方便的實現對數據庫的訪問操作。使用JdbcTemplate可以像JDBC一樣來編寫數據庫的操作代碼
2.為啥要使用Jdbc_template進行開發呢?
Spring對數據庫的操作在jdbc上面做了深層次的封裝,使用spring的註入功能,可以把DataSource註冊到JdbcTemplate之中。
Spring提供的JdbcTemplate對jdbc做了封裝,大大簡化了數據庫的操作。找到Spring JdbcTemplate源碼,可以看到如下方法:
Connection con = DataSourceUtils.getConnection(getDataSource());
如果直接使用JDBC的話,需要我們加載數據庫驅動、創建連接、釋放連接、異常處理等一系列的動作;繁瑣且代碼看起來不直觀。
此外,Spring提供的JdbcTempate能直接數據對象映射成實體類,不再需要獲取ResultSet去獲取值/賦值等操作,提高開發效率;
3.配置環境
①導入jar包
[1]IOC容器需要的jar包
commons-logging-1.1.3.jar
spring-aop-4.0.0.RELEASE.jar //註解會使用到的包
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
[2]MySQL驅動、C3P0jar包
c3p0-0.9.1.2.jar
mysql-connector-java-5.1.37-bin.jar
[3]JdbcTemplate需要的jar包
spring-jdbc-4.0.0.RELEASE.jar
spring-orm-4.0.0.RELEASE.jar
spring-tx-4.0.0.RELEASE.jar
②在IOC容器中配置數據源
聲明式事務: 1.導jar包: - com.springsource.net.sf.cglib-2.2.0.jar - com.springsource.org.aopalliance-1.0.0.jar - com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar - commons-logging-1.1.3.jar - spring-aop-4.0.0.RELEASE.jar - spring-aspects-4.0.0.RELEASE.jar - spring-beans-4.0.0.RELEASE.jar - spring-context-4.0.0.RELEASE.jar - spring-core-4.0.0.RELEASE.jar - spring-expression-4.0.0.RELEASE.jar - spring-jdbc-4.0.0.RELEASE.jar - spring-orm-4.0.0.RELEASE.jar - spring-tx-4.0.0.RELEASE.jar - c3p0-0.9.1.2.jar - mysql-connector-java-5.1.37-bin.jar 2.創建一份jdbc.properties文件 : jdbc.user=root jdbc.passowrd=123456 jdbc.url=jdbc:mysql://localhost:3306/tx jdbc.driver=com.mysql.jdbc.Driver 3.配置文件,在 JdbcTemplate的配置信息的基礎上
事務的設置 - 事務的傳播行為
- 事務的隔離級別
- 事務根據什麽異常不進行回滾
- 事務的超時屬性
- 事務的只讀屬性 事務的傳播行為: -----propagation - REQUIRED :【默認】 如果外層有事務,就會調用外層的事務 如果外層沒事務,就自己啟動個事務,在自己的事務中運行 當第二個發生異常,第一個也跟著回滾
事務的隔離級別: ----- isolation 比如,當前數據庫中的price 為100,輸出100; 然後將 price 改為 200,再輸出還會是100,默認是REPEATABLE_READ,保持事務數據一致性 改為:READ_COMMITTED,就會隨著數據庫中的數據的更改,輸出也更改
<!-- 加載properties文件中 信息 --> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 配置數據源 --> <bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> <property name="driverClass" value="${jdbc.driver}"></property> <property name="jdbcUrl" value="${jdbc.url}"></property> </bean>
其中,新建 jdbc.properties 文件
內容:
jdbc.username=root jdbc.password=123456 jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/jdbc_template
③在IOC容器中配置JdbcTemplate對象的bean,並將數據源對象裝配到JdbcTemplate對象中
<!-- 配置JdbcTemplate對應的bean, 並裝配dataSource數據源屬性--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="comboPooledDataSource"></property> </bean>
實驗1:測試數據源
@Test public void test() throws SQLException{ ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); DataSource bean = ioc.getBean(DataSource.class); System.out.println(bean.getConnection()); }
實驗2:將emp_id=5的記錄的salary字段更新為1300.00【更新操作】
不用我們自己再去獲取數據庫連接信息了,而是直接傳遞sql語句及其參數!public class TestJDBCTemplate { private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); private JdbcTemplate bean = ioc.getBean(JdbcTemplate.class); @Test public void test() throws SQLException{ String sql = "UPDATE employee SET salary = ? WHERE emp_id = ?"; bean.update(sql, 1300,5);//第一個是sql語句,後面的按著順序傳入參數即可,這個update方法是接收的可變參數! } }
實驗3:批量插入 bean.batchUpdate(sql, list);
public class TestJDBCTemplate { private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); private JdbcTemplate bean = ioc.getBean(JdbcTemplate.class); @Test public void test() throws SQLException{ String sql="INSERT INTO employee(emp_name,salary) VALUES(?,?)"; List<Object[]> list = new ArrayList<Object[]>(); list.add(new Object[]{"Tom2015",1000}); list.add(new Object[]{"Tom2016",2000}); list.add(new Object[]{"Tom2017",3000}); bean.batchUpdate(sql, list); } }
實驗4:查詢emp_id=5的數據庫記錄,封裝為一個Java對象返回 分析:封裝為一個對象返回的話,首先我們需要有一個與數據表對應的實體類!
public class TestJDBCTemplate { private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); private JdbcTemplate bean = ioc.getBean(JdbcTemplate.class); @Test public void test(){ //需要註意的是:sql語句中的別名要與對應實體類的屬性名保持一致! String sql = "SELECT emp_id AS empId,emp_name AS empName,salary FROM employee WHERE emp_id=?"; //RowMapper是一個接口,這裏我們使用其子類 RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<Employee>(Employee.class); //最後一個參數是可變參數,用於向sql語句中依次傳遞參數! Employee employee = bean.queryForObject(sql, rowMapper, 5); System.out.println(employee); } }
實驗5:查詢salary>4000的數據庫記錄,封裝為List集合返回
可以看出,查詢結果是一個實體還是一個list列表是靠bean對象的不同方法實現的!public class TestJDBCTemplate { private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); private JdbcTemplate bean = ioc.getBean(JdbcTemplate.class); @Test public void test(){ //需要註意的是:sql語句中的別名要與對應實體類的屬性名保持一致! String sql = "SELECT emp_id AS empId,emp_name AS empName,salary FROM employee WHERE salary > ?"; //RowMapper是一個接口,這裏我們使用其子類 RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<Employee>(Employee.class); //該query方法查詢出來的是一個list列表,query方法的最後一個參數是可變參數! List<Employee> list = bean.query(sql, rowMapper, 4000); for (Employee employee : list) { System.out.println(employee); } } }
實驗6:查詢最大salary
public class TestJDBCTemplate { private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); private JdbcTemplate bean = ioc.getBean(JdbcTemplate.class); @Test public void test01(){ String sql = "SELECT MAX(salary) FROM employee"; //需要指定返回值的類型,而且類型必須是包裝類型 Double maxSalary = bean.queryForObject(sql, Double.class); System.out.println(maxSalary); } }
實驗7:使用帶有具名參數的SQL語句插入一條員工記錄,並以Map形式傳入參數值
具名參數:是指基於名稱的,前面我們使用的都是用 ? 作為占位符,是基於位置的! 如果要使用具名參數的sql語句就必須在spring配置文件中配置NamedParameterJdbcTemplat這個模板類 原來的JdbcTemplate,因為JdbcTemplate不能完成這樣的任務! 在 applicationContext.xml 文件中繼續配置<!-- 為了執行帶有具名參數的SQL語句,需要配置NamedParameterJdbcTemplate --> <!-- 該NamedParameterJdbcTemplate類沒有無參構造器,需要傳入JdbcTemplate對象或者數據源對象[DataSource] --> <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"> <!-- 不能使用property標簽配置 --> <constructor-arg ref="jdbcTemplate"></constructor-arg> </bean>
public class TestJDBCTemplate { private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); @Test public void test01(){ NamedParameterJdbcTemplate bean2 = ioc.getBean(NamedParameterJdbcTemplate.class); String sql="INSERT INTO employee(emp_name,salary) VALUES(:paramName,:paramSalary)";//具名參數與map 的key 值相同 Map<String,Object> paramMap = new HashMap<String,Object>(); paramMap.put("paramName","張學友" ); paramMap.put("paramSalary",1000); bean2.update(sql, paramMap); } }
實驗8:重復實驗7,以SqlParameterSource形式傳入參數值
public class TestJDBCTemplate { private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); @Test public void test01(){ NamedParameterJdbcTemplate bean2 = ioc.getBean(NamedParameterJdbcTemplate.class); String sql="INSERT INTO employee(emp_name,salary) VALUES(:empName,:salary)"; //該BeanPropertySqlParameterSource類構造器需要一個對象參數,該對象參數是一個封裝了sql語句參數的對象! //此時要求對象的屬性名要和sql中的參數名保持一致!這裏我們使用Employee對象來完成 Employee employee= new Employee("郭富城", 1500); //以實體對象的形式封裝具名參數和值 SqlParameterSource source = new BeanPropertySqlParameterSource(employee); bean2.update(sql, source); } }
實驗9:創建JdbcTemplateDao,自動裝配JdbcTemplate對象 1.創建dao類:
@Repository public class JdbcTemplateDao { @Autowired private JdbcTemplate jdbcTemplate; public void update(){ String sql = "DELETE FROM employee where emp_id = ?"; jdbcTemplate.update(sql, 15); } }
2.測試該dao
public class TestJDBCTemplate { private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); private JdbcTemplate bean = ioc.getBean(JdbcTemplate.class); private JdbcTemplateDao dao = ioc.getBean(JdbcTemplateDao.class); @Test public void testEmployeeDao(){ dao.update(); } }
聲明式事務: 1.導jar包: - com.springsource.net.sf.cglib-2.2.0.jar - com.springsource.org.aopalliance-1.0.0.jar - com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar - commons-logging-1.1.3.jar - spring-aop-4.0.0.RELEASE.jar - spring-aspects-4.0.0.RELEASE.jar - spring-beans-4.0.0.RELEASE.jar - spring-context-4.0.0.RELEASE.jar - spring-core-4.0.0.RELEASE.jar - spring-expression-4.0.0.RELEASE.jar - spring-jdbc-4.0.0.RELEASE.jar - spring-orm-4.0.0.RELEASE.jar - spring-tx-4.0.0.RELEASE.jar - c3p0-0.9.1.2.jar - mysql-connector-java-5.1.37-bin.jar 2.創建一份jdbc.properties文件 : jdbc.user=root jdbc.passowrd=123456 jdbc.url=jdbc:mysql://localhost:3306/tx jdbc.driver=com.mysql.jdbc.Driver 3.配置文件,在 JdbcTemplate的配置信息的基礎上
<context:component-scan base-package="com.neuedu"></context:component-scan> <context:property-placeholder location="classpath:jdbc.properties"/> <bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> <property name="driverClass" value="${jdbc.driver}"></property> <property name="jdbcUrl" value="${jdbc.url}"></property> </bean> <!-- 以上時JdbcTemplate配置所需 --> <!-- 配置事務管理器 --> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="comboPooledDataSource"></property> </bean> <!-- 開啟基於註解的聲明式事務 --> <!-- 有時候不需要transaction-manager 因為默認值為transactionManager --> <!-- 如果事務管理器的id為transactionManager就不用寫 --> <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
4.在需要進行事務控制的方法上加註解:@Transactional 5.測試數據源
public class TestDataSource { private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); @Test public void test() throws Exception { DataSource bean = ioc.getBean(DataSource.class); System.out.println(bean.getConnection()); } }
6.創建 Dao 類 Dao 層的四個方法
@Repository public class BookDao { @Autowired private JdbcTemplate template; public double findPriceByIsbn(String isbn){ String sql = "SELECT price FROM book WHERE isbn = ?"; Double price = template.queryForObject(sql, Double.class, isbn); return price; } public void updateBookStock(String isbn){ String sql="UPDATE book_stock SET stock = stock -1 WHERE isbn = ?"; template.update(sql, isbn); } public void updateAccount(String username,Double price){ String sql = "UPDATE account SET balance = balance - ? WHERE username = ?"; template.update(sql, price,username); } //演示事務的傳播機制 public void updateBookPrice(double price,String isbn){ String sql ="UPDATE book SET price = ? WHERE isbn = ?"; template.update(sql, price,isbn); } }
7.創建 Service 層
@Service public class BookService { @Autowired private BookDao bookDao; @Transactional public void doCash(String isbn,String username){ double price = bookDao.findPriceByIsbn(isbn); bookDao.updateBookStock(isbn); try { Thread.sleep(1000*5); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } bookDao.updateAccount(username, price); } @Transactional public void updateBookPrice(double price,String isbn){ bookDao.updateBookPrice(price, isbn); } }
8.測試 Service 中的 doCash 方法:
public class TestBookService { private ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); @Test public void test() { BookService bean = ioc.getBean(BookService.class); bean.doCash("ISBN-001", "Tom"); } }
事務的設置 - 事務的傳播行為
- 事務的隔離級別
- 事務根據什麽異常不進行回滾
- 事務的超時屬性
- 事務的只讀屬性 事務的傳播行為: -----propagation - REQUIRED :【默認】 如果外層有事務,就會調用外層的事務 如果外層沒事務,就自己啟動個事務,在自己的事務中運行 當第二個發生異常,第一個也跟著回滾
@Transactional(propagation=Propagation.REQUIRED) public void doCash(String isbn,String username){ double price = bookDao.findPriceByIsbn(isbn); bookDao.updateBookStock(isbn); bookDao.updateAccount(username, price); } @Transactional(propagation=Propagation.REQUIRED) public void updateBookPrice(double price,String isbn){ bookDao.updateBookPrice(price, isbn); }
- REQUIRES_NEW: 一定會運行自己的事務 如果外層有事務,那就掛起外層事務 等到自己的事務運行完畢,在繼續外層事務 當第二個發生異常,也不會回滾
@Transactional(propagation=Propagation.REQUIRES_NEW) public void doCash(String isbn,String username){ double price = bookDao.findPriceByIsbn(isbn); bookDao.updateBookStock(isbn); bookDao.updateAccount(username, price); } @Transactional(propagation=Propagation.REQUIRES_NEW) public void updateBookPrice(double price,String isbn){ bookDao.updateBookPrice(price, isbn); }
- SUPPORTS : 如果外層有事務,那麽就在外層事務中運行 如果沒有,自己也不啟動新事務 總結:
事務的隔離級別: ----- isolation 比如,當前數據庫中的price 為100,輸出100; 然後將 price 改為 200,再輸出還會是100,默認是REPEATABLE_READ,保持事務數據一致性 改為:READ_COMMITTED,就會隨著數據庫中的數據的更改,輸出也更改
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITTED) public void doCash(String isbn,String username){ double price = bookDao.findPriceByIsbn(isbn); System.out.println(price); bookDao.updateBookStock(isbn); bookDao.updateAccount(username, price); }
不回滾: ------- noRollbackFor={ ArithmeticExpection.class } 默認出現異常回滾,設置之後出現異常不回滾 花?括號內寫異常名稱
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITTED ,noRollbackFor={ArithmeticException.class} ) public void doCash(String isbn,String username){ double price = bookDao.findPriceByIsbn(isbn); bookDao.updateBookStock(isbn); int i = 10/0;//異常 bookDao.updateAccount(username, price); }
事務的超時屬性: ------ tiomeout 事務超時=事務開始時到最後一個Statment創建時時間 + 最後一個Statment 的執行時超市時間 事務超時時間設置為3秒,sql執行時間為1秒時,事務的不超時的 事務超時時間設置為3秒,sql執行時間為5秒時,這樣事務超時!
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITTED ,noRollbackFor={ArithmeticException.class} ,timeout=3) public void doCash(String isbn,String username){ double price = bookDao.findPriceByIsbn(isbn); bookDao.updateBookStock(isbn); try { Thread.sleep(1000*5); } catch (InterruptedException e) { e.printStackTrace(); } bookDao.updateAccount(username, price); }
事務的只讀屬性: -----readOnly 查詢性能更高
Spring(四)-- JdbcTemplate、聲明式事務