天天看點

Spring JDBC-使用Spring JDBC擷取本地連接配接對象以及操作BLOB/CLOB類型資料

  • 概述
  • 如何擷取本地資料連接配接
    • 示例從DBCP資料源中擷取Oracle的本地連接配接對象
  • 相關接口操作
    • LobCreator
    • LobHandler
  • 插入LOB類型的資料
  • 以塊資料的方式讀取LOB資料
  • 以流資料的方式讀取LOB資料
  • 示例源碼

概述

我們在Spring-使用Spring JDBC通路資料庫使用JDBC進行了CRUD(Create Retrieve Update Delete增删改查)以及調用存過的操作,這裡我們将進一步了解一些進階的資料庫操作知識,包括擷取本地資料連接配接進行資料庫相關的操作和如何操作BLOB、CLBO這些LOB資料。

LOB 代表大對象資料,包括 BLOB 和 CLOB 兩種類型。

  • BLOB 用于存儲大塊的二進制資料,如圖檔資料,視訊資料等(議案不宜将檔案存儲到資料中,而應該存儲到專門的檔案伺服器中)
  • CLOB 用于存儲長文本資料,如産品的較長的描述等。

值得注意的是:在不同的資料庫中,大對象對應的字段類型是不盡相同的,如 DB2 對應 BLOB/CLOB,MySql 對應 BLOB/LONGTEXT,SqlServer 對應 IMAGE/TEXT。

需要指出的是,有些資料庫的大對象類型可以象簡單類型一樣通路,如 MySql 的 LONGTEXT 的操作方式和 VARCHAR 類型一樣。在一般情況下, LOB 類型資料的通路方式不同于其它簡單類型的資料,我們經常會以流的方式操作 LOB 類型的資料。

此外,LOB 類型資料的通路不是線程安全的,需要為其單獨配置設定相應的資料庫資源,并在操作完成後釋放資源。

Spring 在 org.springframework.jdbc.support.lob 包中為我們提供了相應的幫助類,以便解決上述疑難雜症。

Spring 大大降低了我們處理 LOB 資料的難度。

  • 首先,Spring 提供了 NativeJdbcExtractor 接口,我們可以在不同環境裡選擇相應的實作類從資料源中擷取本地 JDBC對象;
  • 其次,Spring 通過 LobCreator 接口取消了不同資料廠商操作 LOB 資料的差别,并提供了建立 LobCreator 和LobHandler 接口,我們隻要根據底層資料庫類型選擇合适的 LobHandler 進行配置即可。

如何擷取本地資料連接配接

我們知道,在 Web 應用伺服器或 Spring 中配置資料源時,從資料源中傳回的資料連接配接對象是本地 JDBC 對象(如 DB2Connection、OracleConnection)的代理類,這是因為資料源需要改變資料連接配接原有的行為以便施加額外的控制,比如在調用Connection#close()方法時,将資料連接配接返還到連接配接池中而非将其關閉。

但是我們在某些情況下,希望得到被代理前的本地JDBC對象,比如OracleConnection或者OracleResultSet,以便調用這些驅動程式廠商相關的API完成一些特殊的操作。

為了擷取本地JDBC對象,Spring在org.framework.jdbc.support.nativejdbc包下定義了NativeJdbcExtractor接口并提供了實作類。 NativeJdbcExtractor接口定義了從資料源JDBC對象抽取本地JDBC對象的方法。

我們來看下NativeJdbcExtractor接口幾個重要的方法

  • Connection getNativeConnection(Connection con)

    擷取本地 Connection 對象
  • Connection getNativeConnectionFromStatement(Statement stmt)

    擷取本地 Statement 對象
  • PreparedStatement getNativePreparedStatement(PreparedStatement ps)

    擷取本地 PreparedStatement 對象
  • ResultSet getNativeResultSet(ResultSet rs)

    擷取本地 ResultSet 對象
  • CallableStatement getNativeCallableStatement(CallableStatement cs)

    擷取本地 CallableStatement 對象

有些簡單的資料源僅對 Connection 對象進行代理,這時可以直接使用 SimpleNativeJdbcExtractor 實作類。但有些資料源(如 Jakarta Commons DBCP)會對所有的 JDBC 對象進行代理,這時,就需要根據具體的情況選擇适合的抽取器實作類了。下表列出了不同資料源本地 JDBC 對象抽取器的實作類:

資料源類型 本地JDBC對象抽取類
WebSphere 4 及以上版本的資料源 org.springframework.jdbc.support.nativejdbc.WebSphereNativeJdbcExtractor
WebLogic 6.1+ 及以上版本的資料源 org.springframework.jdbc.support.nativejdbc.WebLogicNativeJdbcExtractor
JBoss 3.2.4 及以上版本的資料源 org.springframework.jdbc.support.nativejdbc.JBossNativeJdbcExtractor
C3P0 資料源 org.springframework.jdbc.support.nativejdbc.C3P0NativeJdbcExtractor
DBCP 資料源 org.springframework.jdbc.support.nativejdbc.CommonsDbcpNativeJdbcExtractor
ObjectWeb 的 XAPool 資料源 org.springframework.jdbc.support.nativejdbc.XAPoolNativeJdbcExtractor

示例:從DBCP資料源中擷取Oracle的本地連接配接對象

package com.xgj.dao.lob.nativeConn;

import java.sql.Connection;
import java.sql.SQLException;

import oracle.jdbc.driver.OracleConnection;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Repository;

/**
 * 
 * 
 * @ClassName: ArtisanDaoImpl
 * 
 * @Description: @Repository标注DAO層
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年9月28日 下午5:35:06
 */

@Repository
public class ArtisanDaoImpl {

    private JdbcTemplate jdbcTemplate;

    /**
     * 
     * 
     * @Title: setJdbcTemplate
     * 
     * @Description: 注入JdbcTemplate
     * 
     * @param jdbcTemplate
     * 
     * @return: void
     */
    @Autowired
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    /**
     * 
     * 
     * @Title: getNativeConn
     * 
     * @Description: 要想使這個類正确運作,JdbcTemplate模闆配置必須調整,具體見conf-getLocalConnObj.xml
     * 
     * 
     * @return: void
     */
    public OracleConnection getOracleNativeConn() {
        OracleConnection oracleConnection = null;
        try {
            // 使用DataSourceUtils 從JdbcTemplate中擷取資料連接配接
            Connection connection = DataSourceUtils.getConnection(jdbcTemplate
                    .getDataSource());
            // 使用模闆類的本地JDBC抽取器擷取本地連接配接
            connection = jdbcTemplate.getNativeJdbcExtractor()
                    .getNativeConnection(connection);

            // 強制類型轉換
            oracleConnection = (OracleConnection) connection;
            // 使用本地對象,調用API完成業務操作(此處省略) 比如使用OracleConnection特殊 API操作lob

        } catch (CannotGetJdbcConnectionException | SQLException e) {
            e.printStackTrace();
        }
        return oracleConnection;
    }
}
           

我們通過 DataSourceUtils 擷取目前線程綁定的資料連接配接,為了使用線程上下文相關的事務,通過 DataSourceUtils 從資料源中擷取連接配接是正确的做法,如果直接通過 dateSource 擷取連接配接,則将得到一個和目前線程上下文無關的資料連接配接執行個體。

JdbcTemplate 可以在配置時注入一個本地 JDBC 對象抽取器,要使上述代碼 正确運作,我們必須進行如下配置:

<?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:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 掃描類包,将标注Spring注解的類自動轉化Bean,同時完成Bean的注入 -->
    <context:component-scan base-package="com.xgj.dao.lob.nativeConn" />

    <!-- 不使用context命名空間,則需要定義Bean 
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 
        <property name="locations" value="classpath:spring/jdbc.properties" /> 
    </bean> 
    -->

    <!-- 使用context命名空間,同上面的Bean等效.在xml檔案中配置資料庫的properties檔案 -->
    <context:property-placeholder location="classpath:spring/jdbc.properties" />

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close" 
        p:driverClassName="${jdbc.driverClassName}"
        p:url="${jdbc.url}" 
        p:username="${jdbc.username}" 
        p:password="${jdbc.password}" />


    <!-- 定義DBCP資料源的JDBC本地對象抽取器 -->
    <bean id="nativeJdbcExtractor" class="org.springframework.jdbc.support.nativejdbc.CommonsDbcpNativeJdbcExtractor"></bean>

    <!-- 配置Jdbc模闆   設定抽取器 -->
    <bean id="jdbcTemplate"  lazy-init="true"
          class="org.springframework.jdbc.core.JdbcTemplate"
          p:dataSource-ref="dataSource"
          p:nativeJdbcExtractor-ref="nativeJdbcExtractor"/>

</beans>
           

單元測試

package com.xgj.dao.lob.nativeConn;

import oracle.jdbc.driver.OracleConnection;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class NativeConnTest {

    ClassPathXmlApplicationContext ctx = null;

    @Before
    public void initContext() {
        // 啟動Spring 容器
        ctx = new ClassPathXmlApplicationContext(
                "classpath:com/xgj/dao/lob/nativeConn/conf-getLocalConnObj.xml");
        System.out.println("initContext successfully");
    }

    @Test
    public void testCallProcWithCallableStatementCreator() {

        ArtisanDaoImpl artisanDaoImpl = ctx.getBean("artisanDaoImpl",
                ArtisanDaoImpl.class);

        OracleConnection oracleConnection = artisanDaoImpl
                .getOracleNativeConn();

        // 檢查對象不為空
        Assert.assertNotNull(oracleConnection);
    }

    @After
    public void closeContext() {
        if (ctx != null) {
            ctx.close();
        }
        System.out.println("close context successfully");
    }
}
           

運作結果

Spring JDBC-使用Spring JDBC擷取本地連接配接對象以及操作BLOB/CLOB類型資料

單元測試通過,說明oracleConnection不為空,我們擷取到了oracleConnection對象。

相關接口操作

LobCreator

雖然 JDBC 定義了兩個操作 LOB 類型的接口:java.sql.Blob 和 java.sql.Clob,但有些廠商的 JDBC 驅動程式并不支援這兩個接口。為此,Spring 定義了一個獨立于 java.sql.Blob/Clob 的 LobCreator 接口,以統一的方式操作各種資料庫的 LOB 類型資料。

因為 LobCreator 本身持有 LOB 所對應的資料庫資源,是以它不是線程安全的,一個 LobCreator 隻能操作一個 LOB 資料。

為了友善在 PreparedStatement 中使用 LobCreator,我們可以直接使用如下方法。

JdbcTemplate#execute(String sql,AbstractLobCreatingPreparedStatementCallback lcpsc)            

下面對 LobCreator 接口中的方法進行簡要說明:

Spring JDBC-使用Spring JDBC擷取本地連接配接對象以及操作BLOB/CLOB類型資料
Spring JDBC-使用Spring JDBC擷取本地連接配接對象以及操作BLOB/CLOB類型資料

LobHandler

LobHandler 接口為操作 BLOB/CLOB 提供了統一通路接口,而不管底層資料庫究竟是以大對象的方式還是以一般資料類型的方式進行操作。此外,LobHandler 還充當了 LobCreator 的工廠類。

大部分資料庫廠商的 JDBC 驅動程式(如 DB2)都以 JDBC 标準的 API 操作 LOB 資料,但 Oracle 9i 及以前的 JDBC 驅動程式采用了自己的 API 操作 LOB 資料,Oracle 9i 直接使用自己的 API 操作 LOB 資料,且不允許通過 PreparedStatement 的

setAsciiStream()、setBinaryStream()、setCharacterStream()

等方法填充流資料。Spring 提供 LobHandler 接口主要是為了遷就 Oracle 特立獨行的作風。是以 Oracle 必須使用 OracleLobHandler 實作類,而其它的資料庫統一使用 DefaultLobHandler 就可以了。

Oracle 10g 改正了 Oracle 9i 這個異化的風格,是以 Oracle 10g 也可以使用 DefaultLobHandler。

下面,我們來看一下 LobHandler 接口的幾個重要方法:

Spring JDBC-使用Spring JDBC擷取本地連接配接對象以及操作BLOB/CLOB類型資料
Spring JDBC-使用Spring JDBC擷取本地連接配接對象以及操作BLOB/CLOB類型資料

插入LOB類型的資料

注意: 我們并不建議将二進制檔案寫入資料庫,該案例僅為示範。

假設我們artisan_lob 表,擁有兩個 LOB 字段和一個ID字段(在應用層使用UUID生成),其中 artisan_detail是 CLOB 類型,而 artisan_attach是 BLOB 類型 。

package com.xgj.dao.lob.dao;

import java.sql.PreparedStatement;
import java.sql.SQLException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.support.AbstractLobCreatingPreparedStatementCallback;
import org.springframework.jdbc.support.lob.LobCreator;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.stereotype.Repository;

import com.xgj.dao.lob.domain.Artisan;

/**
 * 
 * 
 * @ClassName: ArtisanLobDaoImp
 * 
 * @Description: @Repository标注的DAO層
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年9月28日 下午8:15:23
 */

@Repository
public class ArtisanLobDaoImp implements ArtisanLobDao {
    // 定義jdbcTemplate屬性
    private JdbcTemplate jdbcTemplate;
    // 定義LobHander屬性
    private LobHandler lobHandler;

    private static final String addArtisanLobSql = "insert into artisan_lob(artisan_id ,artisan_detail ,artisan_attach) values (?,?,?)";

    // 注入jdbcTemplate
    @Autowired
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    // 注入lobHandler
    @Autowired
    public void setLobHandler(LobHandler lobHandler) {
        this.lobHandler = lobHandler;
    }

    @Override
    public void addArtisanLob(final Artisan artisan) {

        jdbcTemplate.execute(addArtisanLobSql,
                new AbstractLobCreatingPreparedStatementCallback(
                        this.lobHandler) {

                    @Override
                    protected void setValues(PreparedStatement ps,
                            LobCreator lobCreator) throws SQLException,
                            DataAccessException {
                        // 設定ID
                        ps.setString(1, artisan.getArtisanId());
                        // 設定 CLOB 字段
                        lobCreator.setClobAsString(ps, 2,
                                artisan.getArtisanDetail());
                        // 設定 BLOB 字段
                        lobCreator.setBlobAsBytes(ps, 3,
                                artisan.getArtisanAttach());
                    }
                });

    }

}

/**
 * 
 * 
 * JdbcTemplate 中execute和update的差別:
 * 
 * execute不接受參數,無傳回值,适用于create和drop table。
 * 
 * update可以接受參數,傳回值為此次操作影響的記錄數,适合于insert, update, 和delete等操作。
 * 
 */
           

解讀:

首先,我們在 ArtisanLobDaoImp中引入了一個 LobHandler 屬性,并通過

JdbcTemplate#execute(String sql,AbstractLobCreatingPreparedStatementCallback lcpsc)

方法完成插入 LOB 資料的操作。

我們通過匿名内部類的方式定義

LobCreatingPreparedStatementCallback

抽象類的子類,其構造函數需要一個 LobHandler 入參。

在匿名類中實作了父類的抽象方法

setValues(PreparedStatement ps,LobCreator lobCreator)

,在該方法中通過 lobCreator 操作 LOB 對象,我們分别通過字元串和二進制數組填充 BLOB 和 CLOB 的資料

調整 Spring 的配置檔案以配合我們剛剛定義的 ArtisanLobDaoImp。假設底層資料庫是 Oracle,可以采用以下的配置方式:

<?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:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 掃描類包,将标注Spring注解的類自動轉化Bean,同時完成Bean的注入 -->
    <context:component-scan base-package="com.xgj.dao.lob" />


    <!-- 使用context命名空間 配置資料庫的properties檔案 -->
    <context:property-placeholder location="classpath:spring/jdbc.properties" />

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close" 
        p:driverClassName="${jdbc.driverClassName}"
        p:url="${jdbc.url}" 
        p:username="${jdbc.username}" 
        p:password="${jdbc.password}" />

    <!--  nativeJdbcExtractor 和 oracleLobHandler Bean 都設定為 lazy-init="true",
           這是因為 nativeJdbcExtractor 需要通過運作期的反射機制擷取底層的 JDBC 對象,是以需要避免在 Spring 容器啟動時就執行個體化這兩個 Bean -->

    <bean id="nativeJdbcExtractor" class="org.springframework.jdbc.support.nativejdbc.CommonsDbcpNativeJdbcExtractor"
          lazy-init="true"/>

    <bean id="lobHandler" class="org.springframework.jdbc.support.lob.OracleLobHandler" 
          lazy-init="true"
          p:nativeJdbcExtractor-ref="nativeJdbcExtractor"/> <!-- 設定本地 Jdbc 對象抽取器 -->

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
        p:dataSource-ref="dataSource"/>

</beans>
           

解讀:

nativeJdbcExtractor 和 oracleLobHandler Bean 都設定為

lazy-init="true"

,這是因為 nativeJdbcExtractor 需要通過運作期的反射機制擷取底層的 JDBC 對象,是以需要避免在 Spring 容器啟動時就執行個體化這兩個 Bean。

LobHandler 需要通路本地 JDBC 對象,這一任務委托給 NativeJdbcExtractor Bean 來完成,是以我們在為 LobHandler 注入了一個 nativeJdbcExtractor。

最後,我們把 lobHandler Bean 通過掃描注解的方式通過方法注入的方式注入到需要進行 LOB 資料通路操作的 ArtisanLobDaoImp中。(同JdbcTemplate一樣)

// 注入lobHandler
    @Autowired
    public void setLobHandler(LobHandler lobHandler) {
        this.lobHandler = lobHandler;
    }           

如果底層資料庫是 DB2、SQL Server、MySQL 等非 Oracle 的其它資料庫,則隻要簡單配置一個 DefaultLobHandler 就可以了 。 Oracle9 以後也可以采用如下配置。

<bean id="defaultLobHandler" class="org.springframework.jdbc.support.lob.DefaultLobHandler" lazy-init="true"/>           

經驗證,也可以寫入lob。

完整配置檔案如下

<?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:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 掃描類包,将标注Spring注解的類自動轉化Bean,同時完成Bean的注入 -->
    <context:component-scan base-package="com.xgj.dao.lob" />


    <!-- 使用context命名空間 配置資料庫的properties檔案 -->
    <context:property-placeholder location="classpath:spring/jdbc.properties" />

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close" 
        p:driverClassName="${jdbc.driverClassName}"
        p:url="${jdbc.url}" 
        p:username="${jdbc.username}" 
        p:password="${jdbc.password}" />


    <bean id="defaultLobHandler"
        class="org.springframework.jdbc.support.lob.DefaultLobHandler"
        lazy-init="true"/>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
        p:dataSource-ref="dataSource"/>

</beans>
           

DefaultLobHandler 隻是簡單地代理标準 JDBC 的 PreparedStatement 和 ResultSet 對象,由于并不需要通路資料庫驅動本地的 JDBC 對象,是以它不需要 NativeJdbcExtractor 的幫助

單元測試:

package com.xgj.dao.lob.dao;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.UUID;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.FileCopyUtils;

import com.xgj.dao.lob.domain.Artisan;

public class ArtisanDaoLobTest {

    ClassPathXmlApplicationContext ctx = null;

    @Before
    public void initContext() {
        // 啟動Spring 容器
        ctx = new ClassPathXmlApplicationContext(
                "classpath:com/xgj/dao/lob/lobOperation.xml");
        System.out.println("initContext successfully");
    }

    @Test
    public void testCallProcWithCallableStatementCreator() throws IOException {

        ArtisanLobDaoImp artisanLobDaoImp = ctx.getBean("artisanLobDaoImp",
                ArtisanLobDaoImp.class);

        // 執行個體化Artisan
        Artisan artisan = new Artisan();
        // (maven工程資源放在了 resource同名目錄下)

        // 設定主鍵
        artisan.setArtisanId(UUID.randomUUID().toString());

        // 讀取文本檔案,設定給artisanDetail
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        Resource resource = resourcePatternResolver
                .getResource("classpath:com/xgj/dao/lob/dao/artisanDetail.txt");
        InputStream ins = resource.getInputStream();
        // 将 InputStream 轉成String
        // Scanner scanner = new Scanner(ins, "GB2312");
        // String artisanDetail = scanner.useDelimiter("\\A").next();
        String artisanDetail = inputStream2String(ins).toString();
        // 設定給artisanDetail
        artisan.setArtisanDetail(artisanDetail);

        // 讀取圖檔資訊,設定給artisanAttach
        ClassPathResource res = new ClassPathResource(
                "com/xgj/dao/lob/dao/281015.jpg");
        // 讀取圖檔資料
        byte[] artisanAttach = FileCopyUtils.copyToByteArray(res
                .getInputStream());
        artisan.setArtisanAttach(artisanAttach);

        artisanLobDaoImp.addArtisanLob(artisan);
        System.out.println("ADD  BLOB  SUCCESSFULLY ");

        System.out.println("artisan ID:\n" + artisan.getArtisanId());
        System.out.println("artisan detail:\n" + artisan.getArtisanDetail());
        System.out.println("artisan attach length:\n"
                + artisan.getArtisanAttach().length);
    }

    @After
    public void closeContext() {
        if (ctx != null) {
            ctx.close();
        }
        System.out.println("close context successfully");
    }

    public StringBuilder inputStream2String(InputStream ins) throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(ins));
        boolean firstLine = true;
        String line = null;
        while ((line = bufferedReader.readLine()) != null) {
            if (!firstLine) {
                stringBuilder.append(System.getProperty("line.separator"));
            } else {
                firstLine = false;
            }
            stringBuilder.append(line);
        }

        return stringBuilder;
    }
}

           

輸出:

2017-09-28 21:58:48,435  INFO [main] (AbstractApplicationContext.java:583) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@77c55a79: startup date [Thu Sep 28 21:58:48 BOT 2017]; root of context hierarchy
2017-09-28 21:58:48,558  INFO [main] (XmlBeanDefinitionReader.java:317) - Loading XML bean definitions from class path resource [com/xgj/dao/lob/lobOperation.xml]
initContext successfully
ADD  BLOB  SUCCESSFULLY 
artisan ID:
15e396c9-9151-4689-bc4f-e1a7ba5bd55c
artisan detail:
JdbcTemplate 中execute和update的差別: 
execute不接受參數,無傳回值,适用于create和drop table。 
update可以接受參數,傳回值為此次操作影響的記錄數,适合于insert, update, 和delete等操作。
artisan attach length:
74391
2017-09-28 21:58:50,320  INFO [main] (AbstractApplicationContext.java:984) - Closing org.springframework.context.support.ClassPathXmlApplicationContext@77c55a79: startup date [Thu Sep 28 21:58:48 BOT 2017]; root of context hierarchy
close context successfully
           

以塊資料的方式讀取LOB資料

我們可以直接用資料塊的方式讀取 LOB 資料:用 String 讀取 CLOB 字段的資料,用 byte[] 讀取 BLOB 字段的資料

我們新增一個接口,重寫

@Override
    public List<Artisan> selectArtisanById(String artisanId) {

        List<Artisan> artisanList = jdbcTemplate.query(selectArtisanByIdSql,
                new Object[] { artisanId }, new RowMapper<Artisan>() {

                    @Override
                    public Artisan mapRow(ResultSet rs, int rowNum)
                            throws SQLException {
                        // 以二進制數組方式擷取 BLOB 資料。
                        byte[] attach = lobHandler.getBlobAsBytes(rs,
                                "artisan_attach");
                        String artisanDetaiul = lobHandler.getClobAsString(rs,
                                "artisan_detail");
                        Artisan artisan = new Artisan();
                        artisan.setArtisanAttach(attach);
                        artisan.setArtisanDetail(artisanDetaiul);
                        return artisan;
                    }
                });

        return artisanList;
    }           

通過 JdbcTemplate 的

List query(String sql, Object[] args, RowMapper rowMapper)

接口處理行資料的映射。在 RowMapper 回調的 mapRow() 接口方法中,通過 LobHandler 以 byte[] 擷取 BLOB 字段的資料。 getClobAsString擷取CLOB字段。

單元測試代碼:

@Test
    public void getArtisanListById() {
        List<Artisan> artisans = artisanLobDaoImp
                .selectArtisanById("15e396c9-9151-4689-bc4f-e1a7ba5bd55c");

        for (Artisan artisan : artisans) {
            System.out
                    .println("artisan detail:\n" + artisan.getArtisanDetail());

            System.out.println("artisan attach length:\n"
                    + artisan.getArtisanAttach().length);
        }
    }           

測試結果:

2017-09-28 22:20:41,323  INFO [main] (AbstractApplicationContext.java:583) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@9d1649f: startup date [Thu Sep 28 22:20:41 BOT 2017]; root of context hierarchy
2017-09-28 22:20:41,445  INFO [main] (XmlBeanDefinitionReader.java:317) - Loading XML bean definitions from class path resource [com/xgj/dao/lob/lobOperation.xml]
initContext successfully
artisan detail:
One former UN official said the head of the UN in Myanmar (Burma) tried to prevent human rights advocates from visiting sensitive Rohingya areas.
More than 500,000 Rohingya have fled an offensive by the military, with many now sheltering in camps in Bangladesh.
The UN in Myanmar "strongly disagreed" with the BBC findings.
artisan attach length:
74391
2017-09-28 22:20:43,093  INFO [main] (AbstractApplicationContext.java:984) - Closing org.springframework.context.support.ClassPathXmlApplicationContext@9d1649f: startup date [Thu Sep 28 22:20:41 BOT 2017]; root of context hierarchy
close context successfully
           

以流資料的方式讀取LOB資料

由于 LOB 資料可能很大(如 100M),如果直接以塊的方式操作 LOB 資料,需要消耗大量的記憶體資源,對應用程式整體性能産生巨大的沖擊。對于體積很大的 LOB 資料,我們可以使用流的方式進行通路,減少記憶體的占用。

JdbcTemplate 為此提供了一個

Object query(String sql, Object[] args, ResultSetExtractor rse)

方法.

ResultSetExtractor 接口擁有一個處理流資料的抽象類

org.springframework.jdbc.core.support.AbstractLobStreamingResultSetExtractor

,可以通過擴充此類用流的方式操作 LOB 字段的資料。

/**
     * 
     * 
     * @Title: getAttach
     * 
     * @Description: 以流資料方式讀取 LOB 資料
     * 
     * 
     * @return: void
     */
    public void getAttach(final String artisanId, final OutputStream os) {

        jdbcTemplate.query(selectAttachByIdSql, new Object[] { artisanId },
                new AbstractLobStreamingResultSetExtractor<Artisan>() { // 匿名内部類
                    // 處理未找到資料行的情況
                    protected void handleNoRowFound()
                            throws LobRetrievalFailureException {
                        System.out.println("Not Found result!");
                    }

                    // 以流的方式處理 LOB 字段
                    public void streamData(ResultSet rs) throws SQLException,
                            IOException {
                        InputStream is = lobHandler
                                .getBlobAsBinaryStream(rs, 1);
                        if (is != null) {
                            FileCopyUtils.copy(is, os);
                        }
                    }
                });

    }
           

通過擴充 AbstractLobStreamingResultSetExtractor 抽象類,在 streamData(ResultSet rs) 方法中以流的方式讀取 LOB 字段資料。這裡我們又利用到了 Spring 的工具類 FileCopyUtils 将輸入流的資料拷貝到輸出流中。在 getAttach() 方法中通過入參 OutputStream os 接收 LOB 的資料。

我們可以同時覆寫抽象類中的 handleNoRowFound() 方法,定義未找到資料行時的處理邏輯。

單元測試

@Test
    public void getArtisanAttache() throws FileNotFoundException {
        // 定義輸出目的
        OutputStream os = new FileOutputStream(new File("D:/downLoad.jpg"));
        artisanLobDaoImp.getAttach("15e396c9-9151-4689-bc4f-e1a7ba5bd55c", os);
    }
           

示例源碼