AbstractLobTypeHandler.java

/*
 * SPDX-License-Identifier: Apache-2.0
 * See LICENSE file for details.
 *
 * Copyright 2015-2026 the original author or authors.
 */
package org.springframework.orm.ibatis.support;

import com.ibatis.sqlmap.engine.type.BaseTypeHandler;

import java.io.IOException;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.jdbc.support.lob.LobCreator;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.orm.ibatis.SqlMapClientFactoryBean;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

/**
 * Abstract base class for iBATIS TypeHandler implementations that map to LOBs. Retrieves the LobHandler to use from
 * SqlMapClientFactoryBean at config time.
 * <p>
 * For writing LOBs, an active Spring transaction synchronization is required, to be able to register a synchronization
 * that closes the LobCreator.
 * <p>
 * Offers template methods for setting parameters and getting result values, passing in the LobHandler or LobCreator to
 * use.
 *
 * @author Juergen Hoeller
 *
 * @since 1.1.5
 *
 * @see org.springframework.jdbc.support.lob.LobHandler
 * @see org.springframework.jdbc.support.lob.LobCreator
 * @see org.springframework.orm.ibatis.SqlMapClientFactoryBean#setLobHandler
 *
 * @deprecated as of Spring 3.2, in favor of the native Spring support in the Mybatis follow-up project
 *             (https://mybatis.org/)
 */
@Deprecated
public abstract class AbstractLobTypeHandler extends BaseTypeHandler {

  /**
   * Order value for TransactionSynchronization objects that clean up LobCreators. Return
   * DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 200 to execute LobCreator cleanup before JDBC Connection
   * cleanup, if any.
   *
   * @see org.springframework.jdbc.datasource.DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER
   */
  public static final int LOB_CREATOR_SYNCHRONIZATION_ORDER = DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 200;

  private final LobHandler lobHandler;

  /**
   * Constructor used by iBATIS: fetches config-time LobHandler from SqlMapClientFactoryBean.
   *
   * @see org.springframework.orm.ibatis.SqlMapClientFactoryBean#getConfigTimeLobHandler
   */
  public AbstractLobTypeHandler() {
    this(SqlMapClientFactoryBean.getConfigTimeLobHandler());
  }

  /**
   * Constructor used for testing: takes an explicit LobHandler.
   *
   * @param lobHandler
   *          the LobHandler to use
   */
  protected AbstractLobTypeHandler(LobHandler lobHandler) {
    if (lobHandler == null) {
      throw new IllegalStateException(
          "No LobHandler found for configuration - " + "lobHandler property must be set on SqlMapClientFactoryBean");
    }
    this.lobHandler = lobHandler;
  }

  /**
   * This implementation delegates to setParameterInternal, passing in a transaction-synchronized LobCreator for the
   * LobHandler of this type.
   *
   * @see #setParameterInternal
   */
  @Override
  public final void setParameter(PreparedStatement ps, int i, Object parameter, String jdbcType) throws SQLException {
    if (!TransactionSynchronizationManager.isSynchronizationActive()) {
      throw new IllegalStateException("Spring transaction synchronization needs to be active for "
          + "setting values in iBATIS TypeHandlers that delegate to a Spring LobHandler");
    }
    final LobCreator lobCreator = this.lobHandler.getLobCreator();
    try {
      setParameterInternal(ps, i, parameter, jdbcType, lobCreator);
    } catch (IOException ex) {
      throw new SQLException("I/O errors during LOB access: " + ex.getMessage());
    }

    TransactionSynchronizationManager.registerSynchronization(new LobCreatorSynchronization(lobCreator));
  }

  /**
   * This implementation delegates to the getResult version that takes a column index.
   *
   * @see #getResult(java.sql.ResultSet, String)
   * @see java.sql.ResultSet#findColumn
   */
  @Override
  public final Object getResult(ResultSet rs, String columnName) throws SQLException {
    return getResult(rs, rs.findColumn(columnName));
  }

  /**
   * This implementation delegates to getResultInternal, passing in the LobHandler of this type.
   *
   * @see #getResultInternal
   */
  @Override
  public final Object getResult(ResultSet rs, int columnIndex) throws SQLException {
    try {
      return getResultInternal(rs, columnIndex, this.lobHandler);
    } catch (IOException ex) {
      throw new SQLException("I/O errors during LOB access: " + ex.getClass().getName() + ": " + ex.getMessage());
    }
  }

  /**
   * This implementation always throws a SQLException: retrieving LOBs from a CallableStatement is not supported.
   */
  @Override
  public Object getResult(CallableStatement cs, int columnIndex) throws SQLException {
    throw new SQLException("Retrieving LOBs from a CallableStatement is not supported");
  }

  /**
   * Template method to set the given value on the given statement.
   *
   * @param ps
   *          the PreparedStatement to set on
   * @param index
   *          the statement parameter index
   * @param value
   *          the parameter value to set
   * @param jdbcType
   *          the JDBC type of the parameter (as supplied by iBATIS)
   * @param lobCreator
   *          the LobCreator to use
   *
   * @throws SQLException
   *           if thrown by JDBC methods
   * @throws IOException
   *           if thrown by streaming methods
   */
  protected abstract void setParameterInternal(PreparedStatement ps, int index, Object value, String jdbcType,
      LobCreator lobCreator) throws SQLException, IOException;

  /**
   * Resolve the JDBC type name as passed in to
   * {@link #setParameterInternal(PreparedStatement, int, Object, String, LobCreator)}.
   * <p>
   * Subclasses may use this method to normalize or validate the provided JDBC type before applying it to the underlying
   * {@link PreparedStatement}.
   *
   * @param jdbcType
   *          the JDBC type of the parameter, as provided by iBATIS
   *
   * @return the resolved JDBC type (never {@code null}, empty if no type specified)
   */
  protected String resolveJdbcType(String jdbcType) {
    return (jdbcType != null ? jdbcType : "");
  }

  /**
   * Template method to extract a value from the given result set.
   *
   * @param rs
   *          the ResultSet to extract from
   * @param index
   *          the index in the ResultSet
   * @param lobHandler
   *          the LobHandler to use
   *
   * @return the extracted value
   *
   * @throws SQLException
   *           if thrown by JDBC methods
   * @throws IOException
   *           if thrown by streaming methods
   */
  protected abstract Object getResultInternal(ResultSet rs, int index, LobHandler lobHandler)
      throws SQLException, IOException;

  /**
   * Callback for resource cleanup at the end of a Spring transaction. Invokes LobCreator.close to clean up temporary
   * LOBs that might have been created.
   *
   * @see org.springframework.jdbc.support.lob.LobCreator#close
   */
  private static class LobCreatorSynchronization implements TransactionSynchronization {

    private final LobCreator lobCreator;

    public LobCreatorSynchronization(LobCreator lobCreator) {
      this.lobCreator = lobCreator;
    }

    @Override
    public int getOrder() {
      return LOB_CREATOR_SYNCHRONIZATION_ORDER;
    }

    @Override
    public void beforeCompletion() {
      this.lobCreator.close();
    }
  }

}