Hibernate Multi-tenancy + Spring Data JPA执行更新查询| javax.persistence.TransactionRequiredException:执行更新/删除查询

时间:2017-12-19 05:23:53

标签: spring hibernate transactions spring-data-jpa multi-tenant

注意: 鉴于问题的性质和复杂性,我努力在描述问题和提供上下文之间取得平衡。可能,但仍然简洁。请说明需要在何处提供任何其他信息或说明。

问题详情

目前,我有一个简单的SQL UPDATE(如下所示)抛出TransactionRequiredException。必须注意的是,所有finder查询都成功执行,并将适当的结果集返回给服务层,而更新查询则失败并出现错误。

javax.persistence.TransactionRequiredException: Executing an update/delete query

我在存储库类中定义了一个简单的SQL查询,如下所示:

public interface SlaQualifyingItemsRepository extends JpaRepository<SlaQualifyingItemsEntity, RowId>
{
    ... ... ...
    ... ... ...
    @Modifying
    @Transactional(propagation=Propagation.REQUIRED, rollbackFor=RuntimeException.class)
    @Query("UPDATE SlaQualifyingItemsEntity sqie SET sqie.affectedCiId = :affectedCiId WHERE sqie.id.slice=:slice AND sqie.id.rowId IN (:rowIds)")
    public void updateInBulkAffectedService(Integer slice, List<Long> rowIds, Long affectedCiId);
    ... ... ...
    ... ... ...
}

项目配置:

以下仅是项目的pom.xml中配置的关键组件版本。

  • Spring Framework(4.3.7.RELEASE)
  • Spring Data JPA(1.8.2.RELEASE)
  • Hibernate(5.2.9.Final)
  • MS SQL Server 2014
  • jTDS SQL Server驱动程序(1.3.1)
  • Tomcat JDBC Pool(9.0.0.M26)

附加信息:

以下是与项目相关的其他一些相关信息:

  • 我们正在使用Hibernate对多租户实现的支持 每个租户使用DB方法,即每个租户都有自己的物理方法 隔离数据库的数据
  • 由于这是一个多模块/多个 基于子应用程序的项目与多租户启用相关 基础结构类作为单独的库jar文件实现 在主项目中用作依赖项,以支持重用和简化维护。
  • 初始化和 多租户基础设施的引导发生在实际中 消费者申请
  • 数据源(租户数据库)信息,即数据库名称,连接参数,池配置等,保存在我们称为平台数据库的单独数据库中。 hibernate多租户实现从平台数据库中获取信息,以便在应用程序引导期间设置和缓存数据源和连接信息。
  • 在CRUD操作期间,应用程序获取租户标识符以连接到必须执行DAO操作的正确数据源。因此,必须注意,在使用
  • 初始化事务管理器时,关闭(在应用程序引导期间)数据源的自动检测
  

txManager.setAutodetectDataSource(假);

故障排除&amp;调查:

我尝试解决此问题的各种不同的事情,但我始终遇到同样的问题。

  1. 尝试使用传播属性 @Transactional;尝试将其设置为REQUIRED / REQUIRED_NEW
  2. 尝试移动@Transactional注释 - 仅注释Service类方法,注释服务和存储库类方法(使用适当的事务传播设置),仅注释存储库类方法而不更改结果
  3. 尝试使用旧版本的Spring Framework,Spring Data JPA和 Hibernate无济于事
  4. 根据我的调试,我发现就在此之前 试图执行查询Hibernate尝试检查是否 交易已在进行中(在下面的代码中)。具体 指向Spring JPA类将查询执行委托给的地方 Hibernate显示在下面的代码中:
  5. org.springframework.data.jpa.repository.query.JpaQueryExecution

        @Override
        protected Object doExecute(AbstractJpaQuery query, Object[] values) {
    
            int result = query.createQuery(values).executeUpdate();
            ... ... ...
            ... ... ...
    
    1. 尝试进一步调试spring / hibernate层,发现它在org.hibernate.query.internal.AbstractProducedQuery中的以下位置失败。很可能这个问题可能正在发生,因为在评估交易状态时,因为它发现交易状态设置为NOT_ACTIVE,这意味着交易尚未开始。
    2. org.hibernate.query.internal.AbstractProducedQuery

      public int executeUpdate() throws HibernateException {
              if ( ! getProducer().isTransactionInProgress() ) {
                  throw getProducer().getExceptionConverter().convert(
                          new TransactionRequiredException(
                                  "Executing an update/delete query"
                          )
                  );
              }
      

      密钥实施类(包括MTA启用类)

      DatabaseConfig.java

      public abstract class DatabaseConfig
      {
         private static final Logger logger = LogManager.getLogger(DatabaseConfig.class);
      
         @Autowired
         private Environment environment;
      
         @Autowired
         private MapDataSourceLookup dataSourceLookup;
      
         @Bean(name="platformEntityManagerFactory")
         @Qualifier("platformEntityManagerFactory")
         public EntityManagerFactory platformEntityManagerFactory()
         {
            logger.trace("Initializing CSM platform entity manager configurations.");
      
            // Exposes JpaVendorAdapter implementation for Hibernate's Persistence Provider and EntityManager interface
            HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
            jpaVendorAdapter.setDatabasePlatform(environment.getProperty(org.hibernate.cfg.Environment.DIALECT));
            jpaVendorAdapter.setShowSql(Boolean.getBoolean(environment.getProperty(org.hibernate.cfg.Environment.SHOW_SQL)));
      
            // Set up a shared JPA EntityManagerFactory in a Spring application context; the EntityManagerFactory can then be passed to
            // JPA-based DAOs via dependency injection
            LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
      
            // Trigger Spring to scan for classes annotated with @Entity and @MappedSuperclass and automatically add those to the JPA
            // PersistenceUnit
            factory.setPackagesToScan("com.sa.csm.dao.mta.model");
      
            // Specify the JpaVendorAdapter implementation for the desired JPA provider
            factory.setJpaVendorAdapter(jpaVendorAdapter);
      
            // Sets up an embedded data source using Spring’s embedded database support.
            factory.setDataSource(platformDataSource());
            factory.setJpaProperties(jpaProperties());
      
            // Perform initialization after all the properties are set; a misconfiguration will result in an exception being thrown
            factory.afterPropertiesSet();
            return factory.getObject();
         }
      
         /**
          * <p>External configuration properties for a JPA EntityManagerFactory created by Spring.</p>
          * 
          * @return A map containing JPA configuration properties
          */
         private Properties jpaProperties() 
         {
            // Configure Hibernate-based JPA Settings when initializing database sessions
            Properties jpaProperties = new Properties();
            jpaProperties.put(org.hibernate.cfg.Environment.STATEMENT_BATCH_SIZE,
                  environment.getProperty(org.hibernate.cfg.Environment.STATEMENT_BATCH_SIZE));
            jpaProperties.put(org.hibernate.cfg.Environment.STATEMENT_FETCH_SIZE,
                  environment.getProperty(org.hibernate.cfg.Environment.STATEMENT_FETCH_SIZE));
            jpaProperties.put(org.hibernate.cfg.Environment.SHOW_SQL,
                  environment.getProperty(org.hibernate.cfg.Environment.SHOW_SQL));
            jpaProperties.put(org.hibernate.cfg.Environment.FORMAT_SQL,
                  environment.getProperty(org.hibernate.cfg.Environment.FORMAT_SQL));
            jpaProperties.put(org.hibernate.cfg.Environment.MAX_FETCH_DEPTH,
                  environment.getProperty(org.hibernate.cfg.Environment.MAX_FETCH_DEPTH));
            jpaProperties.put(org.hibernate.cfg.Environment.AUTOCOMMIT,
                  environment.getProperty(org.hibernate.cfg.Environment.AUTOCOMMIT));
            jpaProperties.put(org.hibernate.cfg.Environment.USE_SECOND_LEVEL_CACHE,
                  environment.getProperty(org.hibernate.cfg.Environment.USE_SECOND_LEVEL_CACHE));
            jpaProperties.put(org.hibernate.cfg.Environment.HBM2DDL_AUTO, "validate");
            return jpaProperties;
         }
      
         @Bean(name="platformDataSource")
         @Qualifier("platformDataSource")
         public DataSource platformDataSource()
         {
            logger.trace("Initializing CSM platform datasource configurations.");
            DataSource platformDataSource = new DataSource();
            platformDataSource.setDefaultAutoCommit(true);
            platformDataSource.setDefaultReadOnly(false);
            platformDataSource.setDefaultCatalog(environment.getProperty("mta.db.catalog"));
            platformDataSource.setDefaultTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
            platformDataSource.setDriverClassName(environment.getProperty("mta.db.driver"));
            platformDataSource.setUrl(environment.getProperty("mta.db.url"));
            platformDataSource.setUsername(environment.getProperty("mta.db.user"));
            platformDataSource.setPassword(DAOUtils.decrypt(environment.getProperty("mta.db.password")));
      
            return platformDataSource;
         }
      
         @Bean(name="csmDataSources")
         @Qualifier("csmDataSources")
         public Map<String, DataSource> dataSourcesCsm()
         {
            logger.trace("Initializing CSM Platform Datasource.");
            return ((MultiTenantDataSourceLookup)dataSourceLookup).initializeDataSources(platformDataSource());
         }
      
         @Primary
         @Bean(name="platformTransactionManager")
         @Qualifier("platformTransactionManager")
         public PlatformTransactionManager transactionManager()
         {
            logger.trace("Initializing CSM platform transaction manager.");
      
            JpaTransactionManager transactionManager = new JpaTransactionManager();
            transactionManager.setEntityManagerFactory(platformEntityManagerFactory());
            return transactionManager;
         }
      
         @Bean(name="platformSessionFactory")
         @Qualifier("platformSessionFactory")
         public LocalSessionFactoryBean sessionFactory()
         {
            LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
            sessionFactory.setDataSource(platformDataSource());
            sessionFactory.setPackagesToScan(new String[] { "com.sa.csm.dao.mta.model" });
            sessionFactory.setHibernateProperties(jpaProperties());
            return sessionFactory;
         }
      
         @Bean(name="hibernateExceptionTranslator")
         @Qualifier("hibernateExceptionTranslator")
         public HibernateExceptionTranslator hibernateExceptionTranslator()
         {
            logger.trace("Initializing hibernate exception translator.");
            return new HibernateExceptionTranslator();
         }
      
         @Bean(name="poolingStrategy")
         @Qualifier("poolingStrategy")
         public PoolingStrategy poolingStrategy()
         {
            return PoolingStrategy.getPoolingStrategyByCode(environment.getProperty("mta.db.pooling.strategy"));
         }
      }
      

      MultiTenantDatabaseConfig.java

      /**
       * <p>
       * This is the base class that implements configuration of multitenancy with multiple databases and API services using Spring JPA and
       * Hibernate. All the multi-tenant JPA related beans are instantiated from here. During the bootstrapping process, it configures the general
       * JPA infrastructure ((i.e., a DataSource connecting to a database as well as a JPA EntityManagerFactory) for the CSM tenant database
       * access.
       * </p>
       * 
       * @author thada02
       *
       */
      public abstract class MultiTenantDatabaseConfig
      {
         private static final Logger logger = LogManager.getLogger(MultiTenantDatabaseConfig.class);
      
         @Inject
         private Environment environment;
      
         @Bean(name="entityManagerFactoryBean")
         @Qualifier("entityManagerFactoryBean")
         @DependsOn(value={"multiTenantConnectionProvider","currentTenantIdentifierResolver"})
         public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(
               MultiTenantConnectionProvider multitenantConnectionProvider,
               CurrentTenantIdentifierResolver currentTenantIdentifierResolver)
         {
            logger.trace("Initializing entity manager factory bean for enabling multi-tenancy configuration.");
            // Exposes JpaVendorAdapter implementation for Hibernate's Persistence Provider and EntityManager interface
            HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
            jpaVendorAdapter.setDatabasePlatform(environment.getProperty(org.hibernate.cfg.Environment.DIALECT));
            jpaVendorAdapter.setShowSql(Boolean.getBoolean(environment.getProperty(org.hibernate.cfg.Environment.SHOW_SQL)));
      
            // Configure Hibernate-based JPA Settings when initializing database sessions
            Properties jpaProperties = this.jpaProperties();
            jpaProperties.put(org.hibernate.cfg.Environment.MULTI_TENANT_CONNECTION_PROVIDER, multitenantConnectionProvider);
            jpaProperties.put(org.hibernate.cfg.Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver);
      
            // No dataSource is set to resulting entityManagerFactoryBean
            LocalContainerEntityManagerFactoryBean result = new LocalContainerEntityManagerFactoryBean();
      
            // Trigger Spring to scan for classes annotated with @Entity and @MappedSuperclass and automatically add those to the JPA
            // PersistenceUnit
            String[] packagesToScan = environment.getProperty("jpa.entity.packages.to.scan").split(",");
            result.setPackagesToScan(packagesToScan);
      
            // Specify the JpaVendorAdapter implementation for the desired JPA provider
            result.setJpaVendorAdapter(jpaVendorAdapter);
            result.setJpaProperties(jpaProperties);
      
            // Perform initialization after all the properties are set; a misconfiguration will result in an exception being thrown
            result.afterPropertiesSet();
            return result;
         }
      
         /**
          * <p>External configuration properties for a JPA EntityManagerFactory created by Spring.</p>
          * 
          * @return A map containing JPA configuration properties
          */
         private Properties jpaProperties() 
         {
            // Configure Hibernate-based JPA Settings when initializing database sessions
            Properties jpaProperties = new Properties();
            jpaProperties.put(org.hibernate.cfg.Environment.DIALECT, environment.getProperty(org.hibernate.cfg.Environment.DIALECT));
            jpaProperties.put(org.hibernate.cfg.Environment.STATEMENT_BATCH_SIZE, environment.getProperty(org.hibernate.cfg.Environment.STATEMENT_BATCH_SIZE));
            jpaProperties.put(org.hibernate.cfg.Environment.STATEMENT_FETCH_SIZE, environment.getProperty(org.hibernate.cfg.Environment.STATEMENT_FETCH_SIZE));
            jpaProperties.put(org.hibernate.cfg.Environment.DEFAULT_SCHEMA, environment.getProperty(org.hibernate.cfg.Environment.DEFAULT_SCHEMA));
            jpaProperties.put(org.hibernate.cfg.Environment.SHOW_SQL, environment.getProperty(org.hibernate.cfg.Environment.SHOW_SQL));
            jpaProperties.put(org.hibernate.cfg.Environment.FORMAT_SQL, environment.getProperty(org.hibernate.cfg.Environment.FORMAT_SQL));
            jpaProperties.put(org.hibernate.cfg.Environment.MAX_FETCH_DEPTH, environment.getProperty(org.hibernate.cfg.Environment.MAX_FETCH_DEPTH));
            jpaProperties.put(org.hibernate.cfg.Environment.AUTOCOMMIT, environment.getProperty(org.hibernate.cfg.Environment.AUTOCOMMIT));
            jpaProperties.put(org.hibernate.cfg.Environment.USE_SECOND_LEVEL_CACHE, environment.getProperty(org.hibernate.cfg.Environment.USE_SECOND_LEVEL_CACHE));
            jpaProperties.put(org.hibernate.cfg.Environment.HBM2DDL_AUTO, "none");
            jpaProperties.put(org.hibernate.cfg.Environment.MULTI_TENANT, MultiTenancyStrategy.DATABASE.name());
            return jpaProperties;
         }
      
         /**
          * <p>
          * Obtain an instance of the JPA Entity Manager Factory for obtaining the hibernate's session factory.
          * </p>
          * 
          * @param entityManagerFactoryBean  The entity manager factory bean
          * @return An entity manager factory
          */
         @Bean(name="entityManagerFactory")
         @Qualifier("entityManagerFactory")
         @Autowired
         public EntityManagerFactory entityManagerFactory(@Qualifier("entityManagerFactoryBean") LocalContainerEntityManagerFactoryBean entityManagerFactoryBean)
         {
            logger.trace("Initializing entity manager factory associated for multi-tenancy configuration.");
            return entityManagerFactoryBean.getObject();
         }
      
         /**
          * <p>
          * Initialize the hibernate 5 transaction manager using the using the application-managed entity manager factory.
          * </p>
          * 
          * @param entityManagerFactory
          *           The entity manager
          * @return The transaction manager
          */
         @Bean(name="transactionManager")
         @Qualifier("transactionManager")
         public PlatformTransactionManager transactionManager(@Qualifier("entityManagerFactory") EntityManagerFactory entityManagerFactory)
         {
            logger.trace("Initializing transaction manager for multi-tenancy configuration.");
            SessionFactory sessionFactory = entityManagerFactory.unwrap(SessionFactory.class);
            HibernateTransactionManager txManager = new HibernateTransactionManager();
            // The txManager bean needs to unwrap the EntityManagerFactory implementation, Hibernate's SessionFactory in this case, to set the
            // AutodetectDataSource attribute to false, this is a requirement for multitenancy to work
            txManager.setAutodetectDataSource(false);
            txManager.setSessionFactory(sessionFactory);
      
            return txManager;
         }
      
         /**
          * <p>
          * Returns an instance of <code>MultiTenantConnectionProvider</code>
          * </p>
          * 
          * @return An instance of MultiTenantConnectionProvider
          */
         @Bean(name="multiTenantConnectionProvider")
         @Qualifier("multiTenantConnectionProvider")
         public MultiTenantConnectionProvider multiTenantConnectionProvider()
         {
            return new MultiTenantConnectionProviderImpl();
         }
      
         /**
          * <p>
          * Returns an instance of <code>CurrentTenantIdentifierResolver</code>
          * </p>
          * 
          * @return An instance of CurrentTenantIdentifierResolver
          */
         @Bean(name="currentTenantIdentifierResolver")
         @Qualifier("currentTenantIdentifierResolver")
         public CurrentTenantIdentifierResolverImpl currentTenantIdentifierResolver()
         {
            return new CurrentTenantIdentifierResolverImpl();
         }
      }
      

      SlaQualifyingItemsDataAccessService.java

      @Service
      public class SlaQualifyingItemsDataAccessService
      {
         private static final Logger logger = LogManager.getLogger(SlaQualifyingItemsDataAccessService.class);
      
         @Autowired
         private SlaQualifyingItemsRepository slaQualifyingItemsRepository;
      
         ... ... ...
         ... ... ...
         /**
          * <p>Update Affected Service for the SLA Qualifying Items.</p>
          * 
          * @param rowIds List of SLA Qualifying Item Row IDs to update
          * @param affectedCiId The affected service ID value to update 
          */
         public void updateAffectedCiInSlaQualifyingItems(List<Long> rowIds, Long affectedCiId)
         {
            logger.trace("Updating Affected Service in SLA Qualification Item Row IDs#: [{}] with Affected CI ID: {}",
                  rowIds.stream().map(String::valueOf).collect(Collectors.joining(",")), affectedCiId);
            slaQualifyingItemsRepository.updateInBulkAffectedService(CurrentExecutionContext.getTenantIdAsInteger(), rowIds,
                  affectedCiId);
         }
         ... ... ...
         ... ... ...
      

      SlaQualifyingItemsRepository.java

      public interface SlaQualifyingItemsRepository extends JpaRepository<SlaQualifyingItemsEntity, RowId>
      {
          ... ... ...
          ... ... ...
          @Modifying
          @Transactional(propagation=Propagation.REQUIRED, rollbackFor=RuntimeException.class)
          @Query("UPDATE SlaQualifyingItemsEntity sqie SET sqie.affectedCiId = :affectedCiId WHERE sqie.id.slice=:slice AND sqie.id.rowId IN (:rowIds)")
          public void updateInBulkAffectedService(Integer slice, List<Long> rowIds, Long affectedCiId);
          ... ... ...
          ... ... ...
      }
      

0 个答案:

没有答案