三種數據庫訪問——Spring JDBC

本篇隨筆是上兩篇的延續:三種數據庫訪問——原生JDBC數據庫鏈接池:Druidhtml

Spring的JDBC框架

Spring JDBC提供了一套JDBC抽象框架,用於簡化JDBC開發。

Spring主要提供JDBC模板方式、關係數據庫對象化方式、SimpleJdbc方式、事務管理來簡化JDBC編程java

Spring提供了3個模板類:mysql

  • JdbcTemplate:Spring裏最基本的JDBC模板,利用JDBC和簡單的索引參數查詢提供對數據庫的簡單訪問。
  • NamedParameterJdbcTemplate:可以在執行查詢時把值綁定到SQL裏的命名參數,而不是使用索引參數。
  • SimpleJdbcTemplate:利用Java 5的特性,好比自動裝箱、通用(generic)和可變參數列表來簡化JDBC模板的使用。

JdbcTemplate主要提供如下4類方法:spring

  • execute方法:能夠用於執行任何SQL語句,通常用於執行DDL語句;
  • update方法及batchUpdate方法:update方法用於執行新增、修改、刪除等語句;batchUpdate方法用於執行批處理相關語句;
  • query方法及queryForXXX方法:用於執行查詢相關語句;
  • call方法:用於執行存儲過程、函數相關語句。

 

示例項目

接下來,經過一個示例項目來展現如何使用Spring的JDBC框架訪問數據庫。假設該項目的功能有:保存用戶信息、查詢用戶信息。sql

一、首先建立數據庫及用戶表:

CREATE TABLE `user` (
  `id` int(10) NOT NULL auto_increment,
  `name` varchar(30) default NULL,
  `age` int(3) default NULL,
  PRIMARY KEY  (`id`)
)

二、建立工程(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編程

三、實體類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 + "]";
    }
}

四、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());
    }
}

五、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提供了編程式配置事務和聲明式配置事務,其中,聲明式事務有如下兩種方式 :併發

  1. 一種是使用Annotation註解的方式(官方推薦)
  2. 一種是基於Xml的方式

(編程式的事務處理有些侵入性。一般咱們的事務需求並無要求在事務的邊界上進行如此精確的控制,咱們通常採用"聲明式事務"。)app

上面咱們採用了基於註解的方式來配置事務。框架

 

六、配置數據庫鏈接池、配置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),事務不進行回滾。

 

七、測試

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異常進行回滾是很合理的。

 

其餘註解方式:

  1. 修改Spring的默認配置,當發生RuntimeException時,能夠不讓它進行事務回滾 ,只須要加上一個@Transactional(noRollbackFor=RuntimeException.class)
  2. 配置對Exception進行回滾,在方法上添加@Transactional(rollbackFor=Exception.class)
  3. 對於一些查詢工做,由於不須要配置事務支持,咱們給其添加註解: @Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)。readOnly=true表示事務中不容許存在更新操做.

 

關於事務的傳播屬性有下面幾種配置:

  • REQUIRED:業務方法須要在一個事務中運行,若是方法運行時,已經處於一個事務中,那麼加入到該事務中。不然本身建立一個新的事務。(Spring默認的事務傳播屬性)
  • NOT_SUPPORTED:聲明方法不須要事務,若是方法沒有關聯到一個事務,容器不會爲它開啓事務。若是方法在一個事務中被調用,該事務被掛起,在方法調用結束後,原先的事務便會恢復執行
  • REQUIRESNEW:不論是否存在事務,業務方法總會爲本身發起一個新的事務。若是方法運行時已經存在一個事務,則該事務會被掛起,新的事務被建立,直到方法執行結束,新事務才結束,原先的事務才恢復執行.
  • MANDATORY:指定業務方法只能在一個已經存在的事務中執行,業務方法不能本身發起事務,若是業務方法沒有在事務的環境下調用,則容器會拋出異常
  • SUPPORTS:若是業務方法在事務中被調用,則成爲事務中的一部分,若是沒有在事務中調用,則在沒有事務的環境下執行
  • NEVER:指定業務方法絕對不能在事務範圍內運行,不然會拋出異常.
  • NESTED:若是業務方法運行時已經存在一個事務,則新建一個嵌套的事務,該事務能夠有多個回滾點,若是沒有事務,則按REQUIRED屬性執行。 注意:業務方法內部事務的回滾不會對外部事務形成影響,可是外部事務的回滾會影響內部事務

 

總結:
事務是企業應用開發的重要組成部分,它使軟件更加可靠。它們確保一種要麼全有 要麼全無的行爲,防止數據不一致而致使的不可預測的錯誤發生。 事務同時也支持併發,防止併發應用線程在操做同一數據時互相影響。

之前咱們寫Jdbc代碼的時候,可能須要本身手動去開啓事務,而後方法執行結束以後再去提交事務,所有都嵌套在咱們的業務代碼之中,具備很強的侵入性....
使用Spring提供事務管理機制,咱們只須要配置XML或使用Annotion進行註解就能夠實現事務的管理和配置,減小了代碼之間的耦合,配置也很方便,很大程度上提高了咱們的開發效率。

 

參考:http://www.iteye.com/topic/480432

相關文章
相關標籤/搜索