1. 程式人生 > >Spring+JDBC的簡單配置和開發

Spring+JDBC的簡單配置和開發

目前很多的公司採用了spring+jdbc的配置開發專案,下面介紹怎麼配置環境到開發時候的注意事項:
①引入必要的jar檔案
JDBC驅動(mysql為例):
mysql-connector-5.1.7.jar
資料庫連線池(dbcp為例):
commons-dbcp.jar
commons-pool.jar
spring核心必須包:
spring.jar
commons-logging.jar
AOP非必須(方便使用建議新增):
cglib-nodep-2.1_3.jar
aspectjrt.jar
註解(一般都會使用):
common-annotations.jar
aspectjweaver.jar
這裡寫圖片描述

一、配置資料來源dataSource:
學過了spring後,這裡採用spring容器管理來例項化資料來源,一般在配置之前都會開啟事務管理的名稱空間,一般都會使用事務管理。
在配置一個工程的時候一定要注意的是循序漸進,不要一口氣全部配置好所有的東西,這樣如果經驗不豐富的朋友,可能難以解決現有的報錯。所以在配置好資料來源的時候測試一下這個資料來源是否正確,如果經驗比較豐富的可以多配置幾步再測試。

<?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-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"
>
<!-- 開啟註解 --> <context:annotation-config/> <!-- 配置資料連線池 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="url" value="jdbc:mysql:///test?useUnicode=true&amp;characterEncoding=UTF-8"></property> <property name="driverClassName" value="org.gjt.mm.mysql.Driver"></property> <property name="username" value="root"></property> <property name="password" value="heyingxxx"></property> <!-- 連線池初始值 --> <property name="initialSize" value="2"/> <!-- 連線池最大值 --> <property name="maxActive" value="100"/> <!-- 連線池最大空閒值 --> <property name="maxIdle" value="5"/> <!-- 連線池最小空閒值 --> <property name="minIdle" value="2"/> </bean> </beans>

這個dataSource已經交給spring容器管理了,現在使用spring例項化這個bean,以前介紹過方法:

public void testSave() throws Exception {
        try {
            AbstractApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
//          PersonService personService = (PersonService) context.getBean("personService");
            DataSource dataSource = (DataSource) context.getBean("dataSource");
//          personService.save(new Person("heying", 2));
            System.out.println(dataSource.getConnection());
            context.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

這裡寫圖片描述

二、配置業務bean,這邊模擬一個員工的錄入,簡化操作,user只有兩個屬性,id和name

Person.java:

package com.heying.bean;

public class Person {
    private String name;
    private Integer id;

    // 初始化person
    public Person(String name, Integer id) {
        this.name = name;
        this.id = id;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
}

PersonService介面:

package com.heying.service;

import java.util.List;

import com.heying.bean.Person;
public interface PersonService {
    public void save(Person person);
    public void update(Integer id,Person person);
    public List<Person> findAll();
}

PersonService介面的實現類:
業務層面需要呼叫到資料庫,所以需要將dataSource注入到PersonServiceBean,在dataSource無法直接實現資料的增刪改查,如果這裡只是單單的把資料庫注入到PersonServiceBean顯得有些不足,需要通過getConnection等大量的重複動作, 所以在JdbcTemplate中有很多現有的方法,這樣就需要在使用PersonServiceBean的時候不僅需要注入dataSource,還需要初始化JdbcTemplate,這邊可以使用兩個或者多個方式。第一種是前面介紹的在業務bean->PersonServiceBean初始化時候注入dataSource時候採用setter方式,在setter中new JdbcTemplate(dataSource),還有一種就是在例項化業務bean->PersonServiceBean的時候使用init方法,值得注意的是,一定在注入了dataSource之後初始化JdbcTemplate,否則dataSource為null將報錯,因為JdbcTemplate需要資料來源初始化。這邊採用init初始化:

package com.heying.service.impl;

import java.util.List;
import javax.annotation.Resource;
import javax.sql.DataSource;

import com.heying.bean.Person;
import com.heying.service.PersonService;

    public class PersonServiceBean implements PersonService{
        @Resource
        private DataSource dataSource; //注入dataSource到PersonServiceBean
        private JdbcTemplate jdbcTemplate ; // 可以使用init方法初始化,也可以在bean.xml中配置使用setter方法注入PersonServiceBean時候初始化

        public void init(){
        jdbcTemplate = new JdbcTemplate(dataSource);
        }

        @Override
        public void save(Person person) {
            try {
                System.out.println(dataSource.getConnection());
                System.out.println(jdbcTemplate);
                jdbcTemplate.update("insert into t_user (name,id) values (?,?) ", 
                    new Object[]{person.getName(),person.getId()},
                    new int[]{Types.VARCHAR,Types.INTEGER});
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @Override
        public void update(Integer id, Person person) {

        }

        @Override
        public List<Person> findAll() {
            return null;
        }
}

bean.xml檔案:

<bean id="personService" class="com.heying.service.impl.PersonServiceBean" init-method="init"></bean>

測試:

@Test
    public void testSave() throws Exception {
        try {
            AbstractApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
            PersonService personService = (PersonService) context.getBean("personService");
            personService.save(new Person("heying51123", 1102));
            context.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

結果:

這裡寫圖片描述

資料庫:

這裡寫圖片描述

基於註解的事務管理器:

這樣就能完成了一個完整的過程,但是在企業開發中還必須使用事務管理,所以,還必須配置事務管理,防止異常發生時同一個事務中所有的操作回滾,這裡注意的是在spring中的事務管理,預設只回滾RuntimeException()的異常,這類異常不能通過捕獲處理,所以就不需要捕獲的異常是特殊的Exception的子類(unchecked Exception),而其他的異常也是Exception的子類,這些屬於檢查異常,預設是不回滾事務的,所以需要在回滾的方法上定義   @Transactional(rollbackFor=Exception.class):

bean.xml檔案:
事務管理的名稱空間已經引入,要想使事務交給spring管理,需要和註解一樣需要註冊事務管理器,初始化時就需要將dataSource注入到txManager,交給事務管理器管理

        <!-- 註冊事務管理器 -->
        <tx:annotation-driven transaction-manager="txManager"/>

        <!-- 配置事務管理器 -->
        <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
        </bean>

第一次使用insert插入重複主鍵看看資料庫是否回滾:

save方法:

    @Override
    public void save(Person person) throws Exception{
        try {
            System.out.println(dataSource.getConnection());
            System.out.println(jdbcTemplate);
            int updateRows = jdbcTemplate.update("insert into t_user (name,id) values (?,?) ", 
                    new Object[]{person.getName(),person.getId()},
                    new int[]{Types.VARCHAR,Types.INTEGER});
            int updateRows2 = jdbcTemplate.update("insert into t_user (name,id) values (?,?) ", 
                    new Object[]{person.getName(),person.getId()},
                    new int[]{Types.VARCHAR,Types.INTEGER});
            System.out.println("insert影響了: "+updateRows+ " ROWS");
        } catch (Exception e) {
            throw new Exception("普通捕獲異常");
        }
    }

測試:

    public class TestCase {
    private static AbstractApplicationContext context;
    private static PersonService personService;

    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
        context = new ClassPathXmlApplicationContext("bean.xml");
        personService = (PersonService) context.getBean("personService");
    }

    @Test
    public void testSave() throws Exception {
        try {
            personService.save(new Person("heying110", 1102));
        } catch (Exception e) {
            System.err.println("********異常********* "+e.getMessage()); 
        }finally{
            List<Person> list = personService.findAll();
            for (int i = 0; i < list.size(); i++) {
                System.out.println("##-->> "+list.get(i).getName());
                System.out.println("##-->> "+list.get(i).getId());
            }
            context.close();
        }
    }
}

結果:
這裡寫圖片描述

總結: 顯然第二條資料檢查異常並沒有影響第一條資料的插入

第二次加上@Transactional(rollbackFor=Exception.class)定義需要回滾的異常:

    @Transactional(rollbackFor=Exception.class)
    public void save(Person person) {
        fun()...
    }

測試:

personService.save(new Person("heying110", 1103));

結果:

這裡寫圖片描述

1103沒有插入,第二條記錄出現異常,第一條就回滾了,一般資料庫訪問出現的異常比較基層,一般不可能通過try語句快解決的,這樣也有的喜歡丟擲一個執行期異常,不用指明@Transactional(rollbackFor=Exception.class)也能回滾所有出現異常的操作。

不是所有的訪問都需要事務的,比如查詢操作可以不適用事務管理,使用事務管理,必定會影響效能,所以介紹幾種常見的事務傳播屬性(Propagation )的方式:

REQUIRES(預設):加入當前正要執行的事務不在另外一個事務裡,那麼就起一個新的事務
PROPAGATION_SUPPORTS:支援當前事務,如果當前沒有事務,就以非事務方式執行,跟隨呼叫的方法是否有事務決定。
PROPAGATION_MANDATORY:要求在一個已有的事務中執行,業務方法不能發起自己的事務,如果當前沒有事務,就丟擲異常。
PROPAGATION_REQUIRES_NEW:不管業務沒有沒有事務,總會開啟新事務,如果呼叫方法中存在事務,把當前事務掛起,新的事務會建立,直到方法結束後,新事務才算結束,原先掛起的事務恢復執行。
PROPAGATION_NOT_SUPPORTED:以非事務方式執行操作,如果被另外一個方法呼叫,且當前存在事務,就把當前事務掛起,呼叫方法結束後,原先事務在繼續執行。
PROPAGATION_NEVER:要求在一個沒有事務方式中執行,如果存在事務,則丟擲異常,和MANDATORY相反。
NESTED:如果一個活動的事務存在,則執行在一個巢狀的事務中,如果沒有活動的事務,則按照REQUIRES屬性執行,它使用一個單獨的事務,這個事務擁有多個回滾儲存點,內部事務不會對外部事務產生影響,它只對DataSourceTransactionManager事務管理起效。

Spring事務隔離級別:
1. ISOLATION_DEFAULT: 這是一個PlatfromTransactionManager預設的隔離級別,使用資料庫預設的事務隔離級別.
另外四個與JDBC的隔離級別相對應
2. ISOLATION_READ_UNCOMMITTED(讀未提交): 這是事務最低的隔離級別,它充許令外一個事務可以看到這個事務未提交的資料,這種隔離級別會產生髒讀,不可重複讀和幻像讀。
3. ISOLATION_READ_COMMITTED:(讀已提交) 保證一個事務修改的資料提交後才能被另外一個事務讀取。另外一個事務不能讀取該事務未提交的資料
4. ISOLATION_REPEATABLE_READ(可重複讀): 這種事務隔離級別可以防止髒讀,不可重複讀。但是可能出現幻像讀 ,它除了保證一個事務不能讀取另一個事務未提交的資料外,還保證了避免下面的情況產生。
5. ISOLATION_SERIALIZABLE:(序列化) 這是花費最高代價但是最可靠的事務隔離級別。事務被處理為順序執行, 除了防止髒讀,不可重複讀外,還避免了幻像讀。

mysql資料隔離級別藏參考:

介紹一下基於XML方式的事務管理,採用了AOP中的通知管理將事務交給spring管理

        <aop:config>
            <!-- 定義一個切面,攔截的包以及子包類任意類任意方法 -->
            <aop:pointcut id="transactionPointcut" expression="execution(* com.heying.service.PersonService..*.*(..))"/>
            <aop:advisor advice-ref="txtAdvice" pointcut-ref="transactionPointcut"/>
        </aop:config>
        <tx:advice id="txtAdvice" transaction-manager="transactionManager">
            <tx:attributes>
                <tx:method name="get*" read-only="true" propagation="NOT_SUPPORTED"/> <!-- get開頭方法不使用 -->
                <tx:method name="*"/><!-- 其他方法,預設 propagation="REQUIRED" -->                
            </tx:attributes>
        </tx:advice>