Background
I have a Spring Boot 3.0 application that uses Spring Data JPA to call a stored procedure. Here's the Spring Data repository that calls the stored procedure:
public interface CarRepository extends Repository<Car, Long> {
@Procedure(value="pValuation", outputParameterName="reportData")
Blob getReportData(String carTypes, LocalDate tradeDate);
The stored procedure is within a SQL Server 2016 database:
CREATE PROCEDURE [dbo].[pValuation]
@carTypes nvarchar(max),
@tradeDate datetime,
@reportData varbinary(max) out
AS
BEGIN
EXEC RDO.pValuation
@reportData = @reportData out,
@CarTypes = @carTypes ,
@Date = @tradeDate,
@IncludeBaseData = true
END
I use a Blob instead of a byte[] for the out parameter of varbinary(max) because
- The MS SQL JDBC driver (version mssql-jdbc-11.2.3.jre17) returns a max of 8000 bytes when
byte[]is used and - My stored procedure returns > 8000 bytes
I need to convert the Blob into a byte array for additional processing:
@Service
@RequiredArgsConstructor
@Slf4j
public class CarService {
private final CarRepository carRepository;
public Set<CarDto> getValuations(LocalDate date) {
Blob reportData = carRepository.getReportData("sedan,truck", date);
log.debug("got Blob");
try {
byte[] b= reportData.getBytes(1, (int) reportData.length());
log.debug("exception thrown from line above... will never log this");
return carRepository.getCarValuations(b);
}
catch (SQLException convertBlobToBytesException) {
log.error(convertBlobToBytesException.toString());
}
}
Problem
When converting Blob into byte[] via the line:
reportData.getBytes(1, (int) reportData.length())
A SQLExcpetion is thrown. After turning on MSSQL JDBC trace logging, I found that the statement is being closed immediately after getting the Blob (i.e. right after executing this: Blob reportData = carRepository.getReportData("sedan,truck", date);). Here's the log:
2023-09-29T17:33:05.762-05:00 DEBUG 29104 --- [http-nio-8080-exec-1] c.m.s.jdbc.internals.SQLServerException : *** SQLException:SQLServerCallableStatement:119 com.microsoft.sqlserver.jdbc.SQLServerException: The statement is closed. The statement is closed.
2023-09-29T17:33:05.771-05:00 ERROR 29104 --- [http-nio-8080-exec-1] com.example.service.CarService : got Blob
Then the log from TDS.Channel reports closing input stream, closing output stream and then closing TCP socket. Since the call to reportData.getBytes tries to read the bytes from this closed stream, it errors as follows:
2023-09-29T17:33:05.773-05:00 ERROR 29104 --- [http-nio-8080-exec-1] com.example.service.CarService : com.microsoft.sqlserver.jdbc.SQLServerException: The TDS protocol stream is not valid.
Question
How can I get the bytes from the Blob using Spring Data JPA?
What I've tried
I tried wrapping the getValuations method in a transaction but this causes the application to hang when reading packets from stored procedure call.
I can get the bytes without using Spring Data JPA as follows:
@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());
}
}
This code works but I would like to know how to do this using Spring Data JPA.