Spring MVC 中的主/辅助数据源故障转移

时间:2021-04-26 19:00:28

标签: spring database spring-mvc mybatis failover

我有一个在使用 mybatis 的 Spring 框架上开发的 Java Web 应用程序。我看到数据源是在 beans.xml 中定义的。现在我也想添加一个辅助数据源作为备份。例如,如果应用程序无法连接到数据库并出现错误,或者如果服务器关闭,那么它应该能够连接到不同的数据源。 Spring 中是否有配置来执行此操作,或者我们必须在应用程序中手动对其进行编码?

我在 Spring boot 中看到了主要和次要符号,但在 Spring 中什么也没有。如果与主数据源的连接失败/超时,我可以通过连接到辅助数据源在创建/检索连接的代码中实现这些。但是想知道这是否可以通过仅在 Spring 配置中进行更改来实现。

1 个答案:

答案 0 :(得分:0)

让我一一说清楚-

  • Spring Boot 有 @Primary 注释,但没有 @Secondary 注释。
  • @Primary 注释的用途与您所描述的不同。 Spring 不会以任何方式自动切换数据源。 @Primary 只是告诉 spring 使用哪个数据源,以防我们没有在任何事务中指定一个。有关这方面的更多详细信息 - https://www.baeldung.com/spring-data-jpa-multiple-databases

现在,当一个数据源宕机时,我们如何实际切换数据源-

  • 大多数人不会在代码中管理这种高可用性。人们通常更喜欢在主动-被动模式下保持同步的 2 个主数据库实例。对于自动故障转移,可以使用 keepalived 之类的东西。这也是一个高度主观和有争议的话题,这里有很多事情需要考虑,比如我们能否承受复制延迟,是否有为每个主站运行的从站(因为那样我们也必须切换从站,因为旧主站的从站现在会被淘汰)同步等)如果您的数据库分布在各个区域,这将变得更加困难(阅读很棒)并且需要更多的工程、规划和设计。
  • 现在,问题特别提到为此使用应用程序代码。你可以做一件事。 不过,我不建议在生产中使用它。永远。您可以使用自己的自定义注释围绕所有主要事务方法创建 ASPECTJ 建议。让我们将此注释称为 @SmartTransactional 以用于我们的演示。

示例代码。虽然没有测试它-

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SmartTransactional {}


public class SomeServiceImpl implements SomeService {
    @SmartTransactional
    @Transactional("primaryTransactionManager")
    public boolean someMethod(){
        //call a common method here for code reusability or create an abstract class
    }
}

public class SomeServiceSecondaryTransactionImpl implements SomeService {
@Transactional("secondaryTransactionManager")
    public boolean usingTransactionManager2() {
        //call a common method here for code reusability or create an abstract class
    }
}


@Component
@Aspect
public class SmartTransactionalAspect {

    @Autowired
    private ApplicationContext context;

    @Pointcut("@annotation(...SmartTransactional)")
    public void smartTransactionalAnnotationPointcut() {
    }

    @Around("smartTransactionalAnnotationPointcut()")
    public Object methodsAnnotatedWithSmartTransactional(final ProceedingJoinPoint joinPoint) throws Throwable {
        Method method = getMethodFromTarget(joinPoint);
        Object result = joinPoint.proceed();
        boolean failure = Boolean.TRUE;// check if result is failure
        if(failure) {
            String secondaryTransactionManagebeanName = ""; // get class name from joinPoint and append 'SecondaryTransactionImpl' instead of 'Impl' in the class name
            Object bean = context.getBean(secondaryTransactionManagebeanName);
            result = bean.getClass().getMethod(method.getName()).invoke(bean);
        }
        return result;
    }
}