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

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

概述


我们在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 对象抽取器的实现类:


image.png


示例:从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");
    }
}


运行结果


20170929055607179.jpg

单元测试通过,说明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 接口中的方法进行简要说明:


20170929073342158.jpg


20170929073636325.jpg

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


20170929075320541.jpg

20170929075341314.jpg

插入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);
    }

运行结果

20170929104709509.jpg


打开如下(截图):


示例源码

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

相关文章
|
1月前
|
存储 Java API
如何使用 Java 记录简化 Spring Data 中的数据实体
如何使用 Java 记录简化 Spring Data 中的数据实体
35 9
|
1月前
|
JSON 前端开发 Java
【Spring】“请求“ 之传递 JSON 数据
【Spring】“请求“ 之传递 JSON 数据
86 2
|
1月前
|
SQL 分布式计算 关系型数据库
Hadoop-24 Sqoop迁移 MySQL到Hive 与 Hive到MySQL SQL生成数据 HDFS集群 Sqoop import jdbc ETL MapReduce
Hadoop-24 Sqoop迁移 MySQL到Hive 与 Hive到MySQL SQL生成数据 HDFS集群 Sqoop import jdbc ETL MapReduce
84 0
|
1月前
|
SQL 分布式计算 关系型数据库
Hadoop-23 Sqoop 数据MySQL到HDFS(部分) SQL生成数据 HDFS集群 Sqoop import jdbc ETL MapReduce
Hadoop-23 Sqoop 数据MySQL到HDFS(部分) SQL生成数据 HDFS集群 Sqoop import jdbc ETL MapReduce
37 0
|
1月前
|
SQL 分布式计算 关系型数据库
Hadoop-22 Sqoop 数据MySQL到HDFS(全量) SQL生成数据 HDFS集群 Sqoop import jdbc ETL MapReduce
Hadoop-22 Sqoop 数据MySQL到HDFS(全量) SQL生成数据 HDFS集群 Sqoop import jdbc ETL MapReduce
46 0
|
3月前
|
JSON Java API
哇塞!Spring Boot 中的 @DateTimeFormat 和 @JsonFormat,竟能引发数据时间大变革!
【8月更文挑战第29天】在Spring Boot开发中,正确处理日期时间至关重要。
64 1
|
3月前
|
XML JSON Java
使用IDEA+Maven搭建整合一个Struts2+Spring4+Hibernate4项目,混合使用传统Xml与@注解,返回JSP视图或JSON数据,快来给你的SSH老项目翻新一下吧
本文介绍了如何使用IntelliJ IDEA和Maven搭建一个整合了Struts2、Spring4、Hibernate4的J2EE项目,并配置了项目目录结构、web.xml、welcome.jsp以及多个JSP页面,用于刷新和学习传统的SSH框架。
88 0
使用IDEA+Maven搭建整合一个Struts2+Spring4+Hibernate4项目,混合使用传统Xml与@注解,返回JSP视图或JSON数据,快来给你的SSH老项目翻新一下吧
|
3月前
|
Java Spring 开发者
掌握Spring事务管理,打造无缝数据交互——实用技巧大公开!
【8月更文挑战第31天】在企业应用开发中,确保数据一致性和完整性至关重要。Spring框架提供了强大的事务管理机制,包括`@Transactional`注解和编程式事务管理,简化了事务处理。本文深入探讨Spring事务管理的基础知识与高级技巧,涵盖隔离级别、传播行为、超时时间等设置,并介绍如何使用`TransactionTemplate`和`PlatformTransactionManager`进行编程式事务管理。通过合理设计事务范围和选择合适的隔离级别,可以显著提高应用的稳定性和性能。掌握这些技巧,有助于开发者更好地应对复杂业务需求,提升应用质量和可靠性。
44 0
|
28天前
|
Java 关系型数据库 MySQL
mysql5.7 jdbc驱动
遵循上述步骤,即可在Java项目中高效地集成MySQL 5.7 JDBC驱动,实现数据库的访问与管理。
106 1
|
3月前
|
SQL druid Java
Java数据库部分(MySQL+JDBC)(二、JDBC超详细学习笔记)(下)
Java数据库部分(MySQL+JDBC)(二、JDBC超详细学习笔记)
55 3
Java数据库部分(MySQL+JDBC)(二、JDBC超详细学习笔记)(下)

热门文章

最新文章