Spring JdbcTemplate-如何在每个查询之前添加实现多租户的功能?

时间:2018-09-27 16:26:10

标签: java spring jdbc spring-jdbc jdbctemplate

设置

我有一个使用Spring 4.3,JdbcTemplate,Hibernate 5和MySQL 8的应用程序。我已经在每个模式的hibernate中实现了多租户,其中我使用hibernates多租户机制-MultiTenantConnectionProvider来切换模式,并且基本上在那做:

connection.createStatement().execute("USE " + databaseNamePrefix + tenantIdentifier); 

,这有效。

现在,我的应用程序的报告部分使用JdbcTemplate来查询数据库。 现在,我想类似地在JdbcTemplate执行的每个查询之前发出此USE tenantIdentifier语句。

问题

如何为JdbcTemplate执行的每个查询添加一些SQL或语句?

我尝试过的事情

我调查了JdbcTemplate,发现的只有设置NativeJdbcExtractor。我已经尝试了下面的代码,但是他正在通过这种方法登录甚至不是loggin。

@Bean
@DependsOn("dataSource")
public JdbcTemplate jdbcTemplate() {
  JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
  jdbcTemplate.setNativeJdbcExtractor(new SimpleNativeJdbcExtractor(){
     @Override
     public Connection getNativeConnection(Connection con) throws SQLException {
        LOGGER.info("getNativeConnection");
        System.out.println("aaa");
        return super.getNativeConnection(con);
     }

     @Override
     public Connection getNativeConnectionFromStatement(Statement stmt) throws SQLException {
        System.out.println("aaa");
        LOGGER.info("getNativeConnectionFromStatement");
        return super.getNativeConnectionFromStatement(stmt);
     }

  });
  return jdbcTemplate;
}

向Spring添加了功能请求:https://jira.spring.io/browse/SPR-17342

编辑:我看着Spring 5,他们删除了JdbcExtractor,所以这绝对是错误的路径。

3 个答案:

答案 0 :(得分:1)

JdbcTemplate做到这一点将不是一个简单的方法,因为某些方法非常通用,例如execute(ConnectionCallback<T> action)方法允许直接访问java.sql.Connection对象。

为每个租户拥有一个单独的DataSource bean并在Spring中使用合格的自动装配来解决这个问题会更容易。

在打开新的数据库连接时,具有按租户的java.sql.Connection将允许执行USE tenantIdentifier语句(某些池库支持此功能)。由于MySQL USE statement docs可以在每个会话中完成一次:

  

USE db_name语句告诉MySQL将db_name数据库用作后续语句的默认(当前)数据库。数据库将保持默认状态,直到会话结束或发出另一个USE语句为止。

答案 1 :(得分:0)

不创建jdbc模板bean。相反,每次需要执行查询时,都可以使用实体管理器工厂来创建jdbc模板的新实例。 这种方法对我有用。

public class JdbcQueryTemplate {

    public JdbcTemplate getJdbcTemplate(EntityManagerFactory emf) {
        EntityManagerFactoryInfo info = (EntityManagerFactoryInfo) emf;
        return new JdbcTemplate(info.getDataSource());
    }

    public NamedParameterJdbcTemplate getNamedJdbcTemplate(EntityManagerFactory emf) {
        EntityManagerFactoryInfo info = (EntityManagerFactoryInfo) emf;
        return new NamedParameterJdbcTemplate(info.getDataSource());
    }
}

然后使用该类进行查询。

public class Test{

  @Autowired
  private EntityManagerFactory entityManagerFactory;

  public List<Entity> executeQuery() {
      return new JdbcQueryTemplate().getNamedJdbcTemplate(entityManagerFactory)
              .query("query", new BeanPropertyRowMapper<>(Entity.class));
  }
}

答案 2 :(得分:0)

已经晚了,但这是我的解决方案,也许可以帮助某人。不是很优雅,但是效果很好。 对于春季5,我覆盖了JdbcTemplate

import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.*;
import org.springframework.jdbc.datasource.ConnectionHolder;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;

import javax.sql.DataSource;
import java.sql.*;

public class TenantJdbcTemplate extends JdbcTemplate {

    public TenantJdbcTemplate(DataSource dataSource) {
        super(dataSource);
    }

    @Override
    public <T> T execute(StatementCallback<T> action) throws DataAccessException {
        Assert.notNull(action, "Callback object must not be null");

        Connection con = DataSourceUtils.getConnection(obtainDataSource());
        boolean isTransactionalCon = isTransactionalConnection(con, getDataSource());
        Statement stmt = null;
        try {
            stmt = con.createStatement();
            applyStatementSettings(stmt);
            Statement stmtToUse = stmt;
            if (!isTransactionalCon) {
                setSchema(stmtToUse);
            }
            T result = action.doInStatement(stmtToUse);
            handleWarnings(stmt);
            return result;
        }
        catch (SQLException ex) {
            // Release Connection early, to avoid potential connection pool deadlock
            // in the case when the exception translator hasn't been initialized yet.
            String sql = getSql(action);
            JdbcUtils.closeStatement(stmt);
            stmt = null;
            DataSourceUtils.releaseConnection(con, getDataSource());
            con = null;
            throw translateException("StatementCallback", sql, ex);
        }
        finally {
            JdbcUtils.closeStatement(stmt);
            DataSourceUtils.releaseConnection(con, getDataSource());
        }
    }

    @Override
    public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)
                    throws DataAccessException {

        Assert.notNull(psc, "PreparedStatementCreator must not be null");
        Assert.notNull(action, "Callback object must not be null");
        if (logger.isDebugEnabled()) {
            String sql = getSql(psc);
            logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : ""));
        }

        Connection con = DataSourceUtils.getConnection(obtainDataSource());
        boolean isTransactionalCon = isTransactionalConnection(con, getDataSource());

        PreparedStatement ps = null;
        try {
            ps = psc.createPreparedStatement(con);
            applyStatementSettings(ps);
            if (!isTransactionalCon) {
                try (Statement stmt = con.createStatement()) {
                    setSchema(stmt);
                }
            }
            T result = action.doInPreparedStatement(ps);
            handleWarnings(ps);
            return result;
        }
        catch (SQLException ex) {
            // Release Connection early, to avoid potential connection pool deadlock
            // in the case when the exception translator hasn't been initialized yet.
            if (psc instanceof ParameterDisposer) {
                ((ParameterDisposer) psc).cleanupParameters();
            }
            String sql = getSql(psc);
            psc = null;
            JdbcUtils.closeStatement(ps);
            ps = null;
            DataSourceUtils.releaseConnection(con, getDataSource());
            con = null;
            throw translateException("PreparedStatementCallback", sql, ex);
        }
        finally {
            if (psc instanceof ParameterDisposer) {
                ((ParameterDisposer) psc).cleanupParameters();
            }
            JdbcUtils.closeStatement(ps);
            DataSourceUtils.releaseConnection(con, getDataSource());
        }
    }

    @Override
    public <T> T execute(CallableStatementCreator csc, CallableStatementCallback<T> action)
                    throws DataAccessException {

        Assert.notNull(csc, "CallableStatementCreator must not be null");
        Assert.notNull(action, "Callback object must not be null");
        if (logger.isDebugEnabled()) {
            String sql = getSql(csc);
            logger.debug("Calling stored procedure" + (sql != null ? " [" + sql  + "]" : ""));
        }

        Connection con = DataSourceUtils.getConnection(obtainDataSource());
        boolean isTransactionalCon = isTransactionalConnection(con, getDataSource());
        CallableStatement cs = null;
        try {
            cs = csc.createCallableStatement(con);
            applyStatementSettings(cs);
            if (!isTransactionalCon) {
                try (Statement stmt = con.createStatement()) {
                    setSchema(stmt);
                }
            }
            T result = action.doInCallableStatement(cs);
            handleWarnings(cs);
            return result;
        }
        catch (SQLException ex) {
            // Release Connection early, to avoid potential connection pool deadlock
            // in the case when the exception translator hasn't been initialized yet.
            if (csc instanceof ParameterDisposer) {
                ((ParameterDisposer) csc).cleanupParameters();
            }
            String sql = getSql(csc);
            csc = null;
            JdbcUtils.closeStatement(cs);
            cs = null;
            DataSourceUtils.releaseConnection(con, getDataSource());
            con = null;
            throw translateException("CallableStatementCallback", sql, ex);
        }
        finally {
            if (csc instanceof ParameterDisposer) {
                ((ParameterDisposer) csc).cleanupParameters();
            }
            JdbcUtils.closeStatement(cs);
            DataSourceUtils.releaseConnection(con, getDataSource());
        }
    }

    private static void setSchema(Statement stmt) throws SQLException {
        stmt.execute("set search_path=\"" + TenantIdHolder.getTenantId() + "\";");
    }

    private static String getSql(Object sqlProvider) {
        if (sqlProvider instanceof SqlProvider) {
            return ((SqlProvider) sqlProvider).getSql();
        }
        else {
            return null;
        }
    }

    private static boolean isTransactionalConnection(Connection connection, DataSource dataSource) {
        ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
        return conHolder != null && conHolder.getConnection() == connection;
    }

}