简体   繁体   English

Spring 使用 2 个数据源启动“没有正在进行的事务”

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

I have two databases and i'm trying to save some records to both of them inside a service method.我有两个数据库,我正在尝试将一些记录保存到服务方法中。

This gives me the error: 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 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

Here is entities:这是实体:

@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;
}

And here is one of the config files for multiple db connection:这是多个数据库连接的配置文件之一:

@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);
    }
}

And here is the other one:这是另一个:

@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);
    }
}

The problem is about the hist entity.问题在于组织实体。 If i save just the other one it saves.如果我只保存另一个它会保存。

But if i try to save the hist entity like this:但是,如果我尝试像这样保存 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);
}

it saves the first one but it doesn't save the hist and when i look at the logs it just calls a select query and not insert.它保存第一个,但不保存历史记录,当我查看日志时,它只是调用 select 查询而不插入。

And if i try to save the hist entity with saveAndFlush method it gives the error.如果我尝试使用saveAndFlush方法保存 hist 实体,则会出现错误。

What is the reason what can i do about it.我能做些什么的原因是什么。 Is it about config files?是关于配置文件的吗?

You annotate the method with @Transactional , in the background Spring open a transaction with the @Primary transaction manager, in your case the bean named "transactionManager" , corresponding to the "realDataSource" .您使用@Transactional注释该方法,在后台 Spring 使用@Primary事务管理器打开一个事务,在您的情况下是名为"transactionManager"的 bean,对应于"realDataSource" At this stage, only a transaction for the first DB has been opened, and not one for your historization DB, which is why you get this error.在这个阶段,只为第一个数据库打开了一个事务,而不是为您的历史数据库打开了一个事务,这就是您收到此错误的原因。

If you want to open a transaction for your second data source, you have to select the corresponding transaction manager @Transactional(transactionManager = "histTransactionManager")如果你想为你的第二个数据源打开一个事务,你必须 select 对应的事务管理器@Transactional(transactionManager = "histTransactionManager")

Since you can't annotate twice the same method with different @Transactional , you could investigate solution on distributed transaction like Atomikos.由于您不能使用不同的@Transactional注释两次相同的方法,因此您可以研究像 Atomikos 这样的分布式事务的解决方案。 Spring used to provide its own solution ChainedTransactionManager which is now deprecated. Spring 曾经提供自己的解决方案ChainedTransactionManager ,现在已弃用。

Another solution, if possible would be to get rid of the second datasource.如果可能的话,另一个解决方案是摆脱第二个数据源。

Where does your Method call originate from?您的方法调用来自哪里? Can you share all code that is executed before your provided examples?您可以分享在您提供的示例之前执行的所有代码吗? A scheduler for example would not have a SessionContext.例如,调度程序没有 SessionContext。 Is there any reason why you don´t use the autoconfigured datasource?你有什么理由不使用自动配置的数据源吗? That is usually easier.这通常更容易。 I would guess that you are missing我猜你不见了

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

also i am not certain but i think you need a "PlatformTransactionManager" instead of a simple Transaction Manager.我也不确定,但我认为你需要一个“PlatformTransactionManager”而不是一个简单的事务管理器。 You could maybe get useful information from https://www.baeldung.com/spring-boot-configure-multiple-datasources .您可能会从https://www.baeldung.com/spring-boot-configure-multiple-datasources获得有用的信息。 What Spring-Boot version are you on?你用的是什么 Spring-Boot 版本?

Both answers posted by @Airy and @GJohannes is pointing out the parts i'm missing but there is also one thing i needed to add: @Qualifier("histEntityManager") @Airy 和 @GJohannes 发布的两个答案都指出了我缺少的部分,但我还需要添加一件事: @Qualifier("histEntityManager")

Here is the final config file that works for me:这是对我有用的最终配置文件:

@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);
    }
}

And ofcourse i'm adding @Transactional(transactionManager = "histTransactionManager") .当然,我正在添加@Transactional(transactionManager = "histTransactionManager")

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM