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

相关文章
|
3月前
|
JSON Java 数据格式
微服务——SpringBoot使用归纳——Spring Boot返回Json数据及数据封装——封装统一返回的数据结构
本文介绍了在Spring Boot中封装统一返回的数据结构的方法。通过定义一个泛型类`JsonResult&lt;T&gt;`,包含数据、状态码和提示信息三个属性,满足不同场景下的JSON返回需求。例如,无数据返回时可设置默认状态码&quot;0&quot;和消息&quot;操作成功!&quot;,有数据返回时也可自定义状态码和消息。同时,文章展示了如何在Controller中使用该结构,通过具体示例(如用户信息、列表和Map)说明其灵活性与便捷性。最后总结了Spring Boot中JSON数据返回的配置与实际项目中的应用技巧。
191 0
|
3月前
|
JSON Java fastjson
微服务——SpringBoot使用归纳——Spring Boot返回Json数据及数据封装——使用 fastJson 处理 null
本文介绍如何使用 fastJson 处理 null 值。与 Jackson 不同,fastJson 需要通过继承 `WebMvcConfigurationSupport` 类并覆盖 `configureMessageConverters` 方法来配置 null 值的处理方式。例如,可将 String 类型的 null 转为 &quot;&quot;,Number 类型的 null 转为 0,避免循环引用等。代码示例展示了具体实现步骤,包括引入相关依赖、设置序列化特性及解决中文乱码问题。
85 0
|
3月前
|
JSON Java fastjson
微服务——SpringBoot使用归纳——Spring Boot返回Json数据及数据封装——Spring Boot 默认对Json的处理
本文介绍了在Spring Boot中返回Json数据的方法及数据封装技巧。通过使用`@RestController`注解,可以轻松实现接口返回Json格式的数据,默认使用的Json解析框架是Jackson。文章详细讲解了如何处理不同数据类型(如类对象、List、Map)的Json转换,并提供了自定义配置以应对null值问题。此外,还对比了Jackson与阿里巴巴FastJson的特点,以及如何在项目中引入和配置FastJson,解决null值转换和中文乱码等问题。
223 0
|
5月前
|
人工智能 安全 Dubbo
Spring AI 智能体通过 MCP 集成本地文件数据
MCP 作为一款开放协议,直接规范了应用程序如何向 LLM 提供上下文。MCP 就像是面向 AI 应用程序的 USB-C 端口,正如 USB-C 提供了一种将设备连接到各种外围设备和配件的标准化方式一样,MCP 提供了一个将 AI 模型连接到不同数据源和工具的标准化方法。
2671 48
|
4月前
|
XML Java 测试技术
Spring AOP—通知类型 和 切入点表达式 万字详解(通俗易懂)
Spring 第五节 AOP——切入点表达式 万字详解!
228 25
|
5月前
|
存储 NoSQL Java
使用Java和Spring Data构建数据访问层
本文介绍了如何使用 Java 和 Spring Data 构建数据访问层的完整过程。通过创建实体类、存储库接口、服务类和控制器类,实现了对数据库的基本操作。这种方法不仅简化了数据访问层的开发,还提高了代码的可维护性和可读性。通过合理使用 Spring Data 提供的功能,可以大幅提升开发效率。
134 21
|
3月前
|
前端开发 Java 数据库
微服务——SpringBoot使用归纳——Spring Boot集成Thymeleaf模板引擎——Thymeleaf 介绍
本课介绍Spring Boot集成Thymeleaf模板引擎。Thymeleaf是一款现代服务器端Java模板引擎,支持Web和独立环境,可实现自然模板开发,便于团队协作。与传统JSP不同,Thymeleaf模板可以直接在浏览器中打开,方便前端人员查看静态原型。通过在HTML标签中添加扩展属性(如`th:text`),Thymeleaf能够在服务运行时动态替换内容,展示数据库中的数据,同时兼容静态页面展示,为开发带来灵活性和便利性。
93 0
|
3月前
|
XML Java 数据库连接
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——基于 xml 的整合
本教程介绍了基于XML的MyBatis整合方式。首先在`application.yml`中配置XML路径,如`classpath:mapper/*.xml`,然后创建`UserMapper.xml`文件定义SQL映射,包括`resultMap`和查询语句。通过设置`namespace`关联Mapper接口,实现如`getUserByName`的方法。Controller层调用Service完成测试,访问`/getUserByName/{name}`即可返回用户信息。为简化Mapper扫描,推荐在Spring Boot启动类用`@MapperScan`注解指定包路径避免逐个添加`@Mapper`
105 0
|
3月前
|
Java 测试技术 微服务
微服务——SpringBoot使用归纳——Spring Boot中的项目属性配置——少量配置信息的情形
本课主要讲解Spring Boot项目中的属性配置方法。在实际开发中,测试与生产环境的配置往往不同,因此不应将配置信息硬编码在代码中,而应使用配置文件管理,如`application.yml`。例如,在微服务架构下,可通过配置文件设置调用其他服务的地址(如订单服务端口8002),并利用`@Value`注解在代码中读取这些配置值。这种方式使项目更灵活,便于后续修改和维护。
45 0
|
3月前
|
SQL Java 数据库连接
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— application.yml 中对日志的配置
在 Spring Boot 项目中,`application.yml` 文件用于配置日志。通过 `logging.config` 指定日志配置文件(如 `logback.xml`),实现日志详细设置。`logging.level` 可定义包的日志输出级别,例如将 `com.itcodai.course03.dao` 包设为 `trace` 级别,便于开发时查看 SQL 操作。日志级别从高到低为 ERROR、WARN、INFO、DEBUG,生产环境建议调整为较高级别以减少日志量。本课程采用 yml 格式,因其层次清晰,但需注意格式要求。
216 0