hibernate SpringBoot使用2个数据源“没有正在进行的交易”

txu3uszq  于 2022-11-14  发布在  Spring
关注(0)|答案(3)|浏览(138)

我有两个数据库,我正尝试在服务方法中将一些记录保存到这两个数据库中。
这给出了错误: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实体。如果我只救了另一个,它就能救你了。
但如果我尝试这样保存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查询,而不是INSERT。
如果我尝试用saveAndFlush方法保存HIST实体,它会给出错误。
原因是什么?我能做些什么呢?是关于配置文件的吗?

ego6inou

ego6inou1#

您的方法调用源自哪里?您能分享在您提供的示例之前执行的所有代码吗?例如,调度程序不会有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版本?

z31licg0

z31licg02#

您使用@Transactional注解该方法,在后台,Spring使用@Primary事务管理器打开一个事务,在本例中是名为"transactionManager"的Bean,对应于"realDataSource"。在此阶段,只打开了第一个数据库的事务,而没有打开您的历史化数据库的事务,这就是您收到此错误的原因。
如果要为第二个数据源打开事务,则必须选择相应的事务管理器@Transactional(transactionManager = "histTransactionManager")
因为不能用不同的@Transactional注解两次相同的方法,所以可以研究分布式事务的解决方案,如ATOMIKOS。Spring过去提供自己的解决方案ChainedTransactionManager,现在已不推荐使用。
如果可能的话,另一种解决方案是去掉第二个数据源。

bmvo0sr5

bmvo0sr53#

@Ary和@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")

相关问题