Spring(四)-- JdbcTemplate、聲明式事務

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容器中配置數據源
 
<!-- 加載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 文件java

內容:mysql

jdbc.username=root
jdbc.password=123456
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/jdbc_template

 

③在IOC容器中配置JdbcTemplate對象的bean,並將數據源對象裝配到JdbcTemplate對象中spring

<!-- 配置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

不用咱們本身再去獲取數據庫鏈接信息了,而是直接傳遞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:查詢最大salaryexpress

 
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形式傳入參數值app

 
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
 
查詢性能更高
相關文章
相關標籤/搜索