You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Spring Data JPA在无事务时保持数据库连接打开?

问题描述

我有一个Spring Boot 3.0应用,通过Spring Data仓库调用存储过程获取Blob数据,代码如下:

@Service
public class CarService {

    private final CarRepository carRepository;

    public Set<CarDto> getValuations(LocalDate date) {          
        Blob reportData = carRepository.getReportData("sedan,truck", date);
        try {
            byte[] b= reportData.getBytes(1, (int) reportData.length());
            log.debug("connection is closed by Spring Data JPA");
            //...
        } 
        // 省略异常处理
    }
}

public interface CarRepository extends Repository<Car, Long> {
    
    @Procedure(value="pValuation", outputParameterName="reportData")
    Blob getReportData(String carTypes, LocalDate tradeDate);
}

调用后尝试读取Blob字节时出现错误:

SQLException: com.microsoft.sqlserver.jdbc.SQLServerException: The connection is closed. The connection is closed.

原因是Spring Data在仓库方法返回Blob后就关闭了数据库连接。我可以给getValuations方法标记@Transactional来保持连接打开,但这会引发锁问题导致数据库挂起。

限制条件:

  • 不能将getReportData的返回类型改为byte[],因为数据会被截断至8000字节。
  • 存储过程pValuation是只读的。

更新:
直接使用EntityManager调用存储过程无需事务即可正常运行(读取Blob时连接保持打开),代码如下:

@Service
@RequiredArgsConstructor
@Slf4j
public class CarService {
  
    private final CarRepository carRepository;
    private final EntityManager em;

    public Set<CarDto> getValuations(LocalDate date) {
        
        StoredProcedureQuery q = em.createStoredProcedureQuery("pValuation");
        q.registerStoredProcedureParameter("carTypes", String.class, ParameterMode.IN);
        q.registerStoredProcedureParameter("tradeDate", LocalDate.class, ParameterMode.IN);
        q.registerStoredProcedureParameter("reportData", Blob.class, ParameterMode.OUT);
        q.setParameter("carTypes", "sedan,truck");
        q.setParameter("tradeDate", date);
        q.execute();
        
        Blob reportData = (Blob) q.getOutputParameterValue("reportData");
        
        log.debug("got Blob");
        try {
            byte[] b= reportData.getBytes(1, (int) reportData.length());
            log.debug("got bytes");
            return carRepository.getCarValuations(b);
        } 
        catch (SQLException convertBlobToBytesException) {
                log.error(convertBlobToBytesException.toString());
        }
        return Collections.emptySet(); // 补充默认返回
    }
}

需要解决的问题:如何让Spring Data在无事务的情况下保持连接打开,以便读取Blob的字节数据?


解决方案

1. 自定义Spring Data仓库实现,在仓库层读取Blob

Spring Data仓库方法执行时,数据库连接处于打开状态,因此可以在仓库的自定义实现类中完成Blob的读取操作,避免返回Blob到Service层时连接已关闭。

步骤如下:

  • 定义仓库扩展接口:
public interface CarRepositoryCustom {
    byte[] getReportDataAsBytes(String carTypes, LocalDate tradeDate);
}
  • 实现扩展接口:
@Repository
public class CarRepositoryImpl implements CarRepositoryCustom {

    private final EntityManager em;

    public CarRepositoryImpl(EntityManager em) {
        this.em = em;
    }

    @Override
    public byte[] getReportDataAsBytes(String carTypes, LocalDate tradeDate) {
        StoredProcedureQuery q = em.createStoredProcedureQuery("pValuation");
        q.registerStoredProcedureParameter("carTypes", String.class, ParameterMode.IN);
        q.registerStoredProcedureParameter("tradeDate", LocalDate.class, ParameterMode.IN);
        q.registerStoredProcedureParameter("reportData", Blob.class, ParameterMode.OUT);
        q.setParameter("carTypes", carTypes);
        q.setParameter("tradeDate", tradeDate);
        q.execute();

        Blob reportData = (Blob) q.getOutputParameterValue("reportData");
        try {
            return reportData.getBytes(1, (int) reportData.length());
        } catch (SQLException e) {
            throw new RuntimeException("Failed to read Blob data", e);
        } finally {
            try {
                if (reportData != null) {
                    reportData.free();
                }
            } catch (SQLException e) {
                // 记录清理异常或忽略
            }
        }
    }
}
  • 修改原仓库接口继承扩展接口:
public interface CarRepository extends Repository<Car, Long>, CarRepositoryCustom {
    // 保留其他方法(如果有)
}
  • Service层调用新方法:
public Set<CarDto> getValuations(LocalDate date) {          
    byte[] reportBytes = carRepository.getReportDataAsBytes("sedan,truck", date);
    return carRepository.getCarValuations(reportBytes);
}

这种方式利用仓库方法执行时的连接上下文,在内部完成Blob读取,无需额外事务,同时避免了连接关闭导致的异常。

2. 使用原生SQL调用存储过程(替代@Procedure)

如果不想自定义仓库实现,可以尝试用原生SQL调用存储过程,确保Blob读取在查询执行过程中完成:

public interface CarRepository extends Repository<Car, Long> {
    @Query(value = "EXEC pValuation :carTypes, :tradeDate, @reportData OUTPUT", nativeQuery = true)
    @SqlResultSetMapping(
        name = "BlobResult",
        columns = @ColumnResult(name = "reportData", type = Blob.class)
    )
    byte[] getReportData(@Param("carTypes") String carTypes, @Param("tradeDate") LocalDate tradeDate);
}

注意:不同数据库的存储过程调用语法有差异,需根据SQL Server的语法调整。

3. 连接池属性调整(不推荐)

可以尝试调整数据源连接池的holdability属性(如HikariCP的相关配置),强制保持连接打开,但这种方式会影响整个应用的连接池行为,灵活性差,不建议优先使用。


总结

最稳妥且符合Spring Data设计思路的方案是自定义仓库实现,在仓库层完成Blob读取,既不需要额外事务,也避免了连接关闭的问题,同时保持代码可维护性。

内容的提问来源于stack exchange,提问作者James

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 15:01:14