Spring JDBC-使用Spring JDBC获取本地连接对象以及操作BLOB/CLOB类型数据

2021-08-17 10:12:43 浏览数 (1)

  • 概述
  • 如何获取本地数据连接
    • 示例从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的本地连接对象

代码语言:javascript复制
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 对象抽取器,要使上述代码 正确运行,我们必须进行如下配置:

代码语言:javascript复制
<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">

    
    <context:component-scan base-package="com.xgj.dao.lob.nativeConn" />

    

    
    <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="nativeJdbcExtractor" class="org.springframework.jdbc.support.nativejdbc.CommonsDbcpNativeJdbcExtractor">bean>

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

beans>

单元测试

代码语言:javascript复制
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");
    }
}

运行结果

单元测试通过,说明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,我们可以直接使用如下方法。

代码语言:javascript复制
JdbcTemplate#execute(String sql,AbstractLobCreatingPreparedStatementCallback lcpsc) 

下面对 LobCreator 接口中的方法进行简要说明:


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 接口的几个重要方法:


插入LOB类型的数据

注意: 我们并不建议将二进制文件写入数据库,该案例仅为演示。

假设我们artisan_lob 表,拥有两个 LOB 字段和一个ID字段(在应用层使用UUID生成),其中 artisan_detail是 CLOB 类型,而 artisan_attach是 BLOB 类型 。

代码语言:javascript复制
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,可以采用以下的配置方式:

代码语言:javascript复制
<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">

    
    <context:component-scan base-package="com.xgj.dao.lob" />


    
    <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="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"/> 

    <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一样)

代码语言:javascript复制
// 注入lobHandler
    @Autowired
    public void setLobHandler(LobHandler lobHandler) {
        this.lobHandler = lobHandler;
    }

如果底层数据库是 DB2、SQL Server、MySQL 等非 Oracle 的其它数据库,则只要简单配置一个 DefaultLobHandler 就可以了 。 Oracle9 以后也可以采用如下配置。

代码语言:javascript复制
"defaultLobHandler" class="org.springframework.jdbc.support.lob.DefaultLobHandler" lazy-init="true"/>

经验证,也可以写入lob。

完整配置文件如下

代码语言:javascript复制
<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">

    
    <context:component-scan base-package="com.xgj.dao.lob" />


    
    <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 的帮助


单元测试:

代码语言:javascript复制
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;
    }
}

输出:

代码语言:javascript复制
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 字段的数据

我们新增一个接口,重写

代码语言:javascript复制
@Override
    public List selectArtisanById(String artisanId) {

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

                    @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字段。


单元测试代码:

代码语言:javascript复制
@Test
    public void getArtisanListById() {
        List 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);
        }
    }

测试结果:

代码语言:javascript复制
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 字段的数据。

代码语言:javascript复制
/**
     * 
     * 
     * @Title: getAttach
     * 
     * @Description: 以流数据方式读取 LOB 数据
     * 
     * 
     * @return: void
     */
    public void getAttach(final String artisanId, final OutputStream os) {

        jdbcTemplate.query(selectAttachByIdSql, new Object[] { artisanId },
                new AbstractLobStreamingResultSetExtractor() { // 匿名内部类
                    // 处理未找到数据行的情况
                    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() 方法,定义未找到数据行时的处理逻辑。

单元测试

代码语言:javascript复制
@Test
    public void getArtisanAttache() throws FileNotFoundException {
        // 定义输出目的
        OutputStream os = new FileOutputStream(new File("D:/downLoad.jpg"));
        artisanLobDaoImp.getAttach("15e396c9-9151-4689-bc4f-e1a7ba5bd55c", os);
    }

运行结果

打开如下(截图):


示例源码

代码已托管到Github—> https://github.com/yangshangwei/SpringMaster

0 人点赞