簡體   English   中英

Spring 使用 2 個數據源啟動“沒有正在進行的事務”

[英]Spring boot "no transaction is in progress" with 2 datasources

我有兩個數據庫,我正在嘗試將一些記錄保存到服務方法中。

這給了我錯誤: org.springframework.dao.InvalidDataAccessApiUsageException: no transaction is in progress; nested exception is javax.persistence.TransactionRequiredException: no transaction is in progress org.springframework.dao.InvalidDataAccessApiUsageException: no transaction is in progress; nested exception is javax.persistence.TransactionRequiredException: no transaction is in progress

這是實體:

@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "TABLE_NAME")
public class SomeEntity {
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_generator")
  @SequenceGenerator(name = "seq_generator", sequenceName = "SEQ_ID", allocationSize = 1)
  @Id
  @Column(name = "ID", nullable = false)
  private Long id;

  @Column(name = "SOME_STR", nullable = false)
  private String someStr;

  @Column(name = "SOME_INT", nullable = false)
  private Integer someInt;

  public SomeEntity(String someStr, Integer someInt) {
    this.someStr = someStr;
    this.someInt = someInt;
  }
}


@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "TABLE_NAME")
public class SomeEntityHist {
  @Id
  @Column(name = "ID", nullable = false)
  private Long id;

  @Column(name = "SOME_STR", nullable = false)
  private String someStr;

  @Column(name = "SOME_INT", nullable = false)
  private Integer someInt;
}

這是多個數據庫連接的配置文件之一:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "realEntityManager",
        basePackages = {"com.some.project.files.repository.real"}
)
@RequiredArgsConstructor
@Log4j2
@AutoConfigureOrder(1)
public class RealDatasourceConfig {

    private final Environment env;

    @Primary
    @Bean
    public DataSource realDataSource() throws SQLException {
        HikariDataSource hikariDataSource = new HikariDataSource();
        hikariDataSource.setDriverClassName(Objects.requireNonNullElse(env.getProperty("real.driver-class-name"), "oracle.jdbc.OracleDriver"));
        hikariDataSource.setJdbcUrl(env.getProperty("real.db-url"));
        hikariDataSource.setUsername(env.getProperty("real.username"));
        hikariDataSource.setPassword(env.getProperty("real.password"));
        hikariDataSource.setMinimumIdle(Integer.parseInt(Objects.requireNonNullElse(env.getProperty("real.minPoolSize"), "1")));
        hikariDataSource.setMaximumPoolSize(Integer.parseInt(Objects.requireNonNullElse(env.getProperty("real.maxPoolSize"), "10")));
        Properties props = new Properties();
        props.setProperty("maxStatements", env.getProperty("real.maxStatements", "300"));
        hikariDataSource.setDataSourceProperties(props);
        hikariDataSource.setPoolName(env.getProperty("real.pool-name"));
        hikariDataSource.setConnectionTestQuery(env.getProperty("real.connection-test-query"));
        return hikariDataSource;
    }

    @Primary
    @Bean
    public LocalContainerEntityManagerFactoryBean realEntityManager(EntityManagerFactoryBuilder builder) throws SQLException {
        return builder
                .dataSource(realDataSource())
                .packages("com.some.project.files.entity.real")
                .persistenceUnit("real")
                .build();
    }

    @Primary
    @Bean(name = "transactionManager")
    public JpaTransactionManager realTransactionManager(EntityManagerFactory realEntityManager) {
        return new JpaTransactionManager(realEntityManager);
    }
}

這是另一個:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "histEntityManager",
        basePackages = {"com.some.project.files.repository.hist"}
)
@RequiredArgsConstructor
@Log4j2
@AutoConfigureOrder(3)
public class HistDatasourceConfig {

    private final Environment env;


    @Bean
    public DataSource histDataSource() throws SQLException {
        HikariDataSource hikariDataSource = new HikariDataSource();
        hikariDataSource.setDriverClassName(Objects.requireNonNullElse(env.getProperty("hist.driver-class-name"), "oracle.jdbc.OracleDriver"));
        hikariDataSource.setJdbcUrl(env.getProperty("hist.jdbc-url"));
        hikariDataSource.setUsername(env.getProperty("hist.username"));
        hikariDataSource.setPassword(env.getProperty("hist.password"));
        hikariDataSource.setMinimumIdle(Integer.parseInt(Objects.requireNonNullElse(env.getProperty("hist.minPoolSize"), "1")));
        hikariDataSource.setMaximumPoolSize(Integer.parseInt(Objects.requireNonNullElse(env.getProperty("hist.maxPoolSize"), "10")));
        Properties props = new Properties();
        props.setProperty("maxStatements", env.getProperty("hist.maxStatements", "300"));
        hikariDataSource.setDataSourceProperties(props);
        hikariDataSource.setPoolName(env.getProperty("hist.pool-name"));
        hikariDataSource.setConnectionTestQuery(env.getProperty("hist.connection-test-query"));
        return hikariDataSource;
    }

    @Bean("histEntityManager")
    public LocalContainerEntityManagerFactoryBean histEntityManager(EntityManagerFactoryBuilder builder) throws SQLException {
        return builder
                .dataSource(histDataSource())
                .packages("com.some.project.files.entity.hist")
                .persistenceUnit("hist")
                .build();
    }

    @Bean
    public JpaTransactionManager histTransactionManager(EntityManagerFactory histEntityManager) {
        return new JpaTransactionManager(histEntityManager);
    }
}

問題在於組織實體。 如果我只保存另一個它會保存。

但是,如果我嘗試像這樣保存 hist 實體:

@Override
@Transactional
public void someMethod() {
  SomeEntity entity = new SomeEntity("abc", 123);
  SomeRepository.save(entity);

  SomeEntityHist entityHist = new SomeEntityHist(1L, "abc", 123);
  SomeRepositoryHist.save(entityHist);
}

它保存第一個,但不保存歷史記錄,當我查看日志時,它只是調用 select 查詢而不插入。

如果我嘗試使用saveAndFlush方法保存 hist 實體,則會出現錯誤。

我能做些什么的原因是什么。 是關於配置文件的嗎?

您使用@Transactional注釋該方法,在后台 Spring 使用@Primary事務管理器打開一個事務,在您的情況下是名為"transactionManager"的 bean,對應於"realDataSource" 在這個階段,只為第一個數據庫打開了一個事務,而不是為您的歷史數據庫打開了一個事務,這就是您收到此錯誤的原因。

如果你想為你的第二個數據源打開一個事務,你必須 select 對應的事務管理器@Transactional(transactionManager = "histTransactionManager")

由於您不能使用不同的@Transactional注釋兩次相同的方法,因此您可以研究像 Atomikos 這樣的分布式事務的解決方案。 Spring 曾經提供自己的解決方案ChainedTransactionManager ,現在已棄用。

如果可能的話,另一個解決方案是擺脫第二個數據源。

您的方法調用來自哪里? 您可以分享在您提供的示例之前執行的所有代碼嗎? 例如,調度程序沒有 SessionContext。 你有什么理由不使用自動配置的數據源嗎? 這通常更容易。 我猜你不見了

@EnableJpaRepositories(
    entityManagerFactoryRef = "realEntityManager",
    basePackages = {"com.some.project.files.repository.real"}
    transactionManagerRef = "__yourTransactionManagerReference__" <--- you are probably missing this
)

我也不確定,但我認為你需要一個“PlatformTransactionManager”而不是一個簡單的事務管理器。 您可能會從https://www.baeldung.com/spring-boot-configure-multiple-datasources獲得有用的信息。 你用的是什么 Spring-Boot 版本?

@Airy 和 @GJohannes 發布的兩個答案都指出了我缺少的部分,但我還需要添加一件事: @Qualifier("histEntityManager")

這是對我有用的最終配置文件:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "histEntityManager",
        transactionManagerRef = "histTransactionManager",
        basePackages = {"com.some.project.files.repository.hist"}
)
@RequiredArgsConstructor
@Log4j2
@AutoConfigureOrder(3)
public class HistDatasourceConfig {

    private final Environment env;


    @Bean
    public DataSource histDataSource() throws SQLException {
        HikariDataSource hikariDataSource = new HikariDataSource();
        hikariDataSource.setDriverClassName(Objects.requireNonNullElse(env.getProperty("hist.driver-class-name"), "oracle.jdbc.OracleDriver"));
        hikariDataSource.setJdbcUrl(env.getProperty("hist.jdbc-url"));
        hikariDataSource.setUsername(env.getProperty("hist.username"));
        hikariDataSource.setPassword(env.getProperty("hist.password"));
        hikariDataSource.setMinimumIdle(Integer.parseInt(Objects.requireNonNullElse(env.getProperty("hist.minPoolSize"), "1")));
        hikariDataSource.setMaximumPoolSize(Integer.parseInt(Objects.requireNonNullElse(env.getProperty("hist.maxPoolSize"), "10")));
        Properties props = new Properties();
        props.setProperty("maxStatements", env.getProperty("hist.maxStatements", "300"));
        hikariDataSource.setDataSourceProperties(props);
        hikariDataSource.setPoolName(env.getProperty("hist.pool-name"));
        hikariDataSource.setConnectionTestQuery(env.getProperty("hist.connection-test-query"));
        return hikariDataSource;
    }

    @Bean("histEntityManager")
    public LocalContainerEntityManagerFactoryBean histEntityManager(EntityManagerFactoryBuilder builder) throws SQLException {
        return builder
                .dataSource(histDataSource())
                .packages("com.some.project.files.entity.hist")
                .persistenceUnit("hist")
                .build();
    }

    @Bean
    public JpaTransactionManager histTransactionManager(@Qualifier("histEntityManager") EntityManagerFactory histEntityManager) {
        return new JpaTransactionManager(histEntityManager);
    }
}

當然,我正在添加@Transactional(transactionManager = "histTransactionManager")

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM