spring 动态方言与Hibernate和Hikari

hfwmuf9z  于 2023-09-29  发布在  Spring
关注(0)|答案(3)|浏览(130)

我是Hibernate的新手。在我的代码中,到DB的连接是用Hikari数据源管理的。
我的代码现在是多租户的,但它为所有租户管理相同的hibernate方言。

**是否可以创建一个配置,使每个租户都可以使用不同的方言?**方言的类型可以作为租户的属性提供。

以下是实体ManagerFactory的示例:

@Bean
     public LocalContainerEntityManagerFactoryBean entityManagerFactory() {

        Map<String, Object> jpaProperties = new HashMap<>();
        jpaProperties.put(..., ...);
        jpaProperties.put(org.hibernate.cfg.Environment.DIALECT, "myDialect");
        
        LocalContainerEntityManagerFactoryBean emfBean = new LocalContainerEntityManagerFactoryBean();
        emfBean.setPackagesToScan(new String[] {MyEntity.class.getPackage().getName()});
        emfBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
        emfBean.setJpaPropertyMap(jpaProperties);
        return emfBean;
    }

编辑

我在看这个solution:它建议为每个方言创建一个复制的LocalContainerEntityManagerFactoryBean。我不明白的是,我如何分辨何时使用一个EntityManager(MySQL),何时使用另一个(Postgres或MsSQL):solution区分实体(每个实体都有自己的DB),但在我的情况下,所有实体都在所有DB上。是歧视的房客。
举例来说:如果我创建LocalContainerEntityManagerFactoryBean的第二个示例(即msSQLEntityManagerFactory())设置了SQL Server的方言,应用程序无法启动:

Application failed to start due to an exceptionorg.springframework.beans.factory.NoUniqueBeanDefinitionException:
 No qualifying bean of type 'javax.persistence.EntityManagerFactory' available:
 expected single matching bean but found 2: 
msSQLEntityManagerFactory,entityManagerFactory
kmbjn2e3

kmbjn2e31#

这是不可能的,因为方言会影响某些引用规则等。你不能只是在运行时根据租户标识符“换出”。只需创建两个持久性单元,每个持久性单元针对每种数据库类型指向不同的数据源。您将不得不以某种方式根据您的租户标识符查找适当的EntityManager/EntityManagerFactory,这使得当您想要使用Spring Data JPA时变得有点困难,因为它需要实体管理器工厂的编译静态名称引用。也许您可以创建一个自定义的EntityManagerFactory,根据租户标识符将所有方法调用委托给适当的示例。总的来说,这并不是那么容易,你可能要做很多的试验和错误。
IMO最好是有一个单独的应用程序部署与单独的配置,如果可能的话,每个数据库类型。

yrwegjxp

yrwegjxp2#

我终于找到了解决这个问题的办法。我设法通过为每种方言(在本例中为MySQL、Postgres和MS SQL Server)创建一个实体管理器工厂来解决方言的问题。
为EntityManagerFactory创建一个bean,并返回该接口的代理,在处理程序中,根据您的逻辑,您可以切换使用哪个EMF来适应所使用的数据源。
我已经为此创建了一个视频,因为它似乎没有在线文档。
Session Scoped Connection
这与您试图实现的目标非常相似,但在我的情况下,用户提供凭据,因此它更加复杂。

zlhcx6iw

zlhcx6iw3#

@Tsvetelin的回答给我指出了正确的方向,但是,在应用它之后,我开始遇到另一个库的问题,比如Hazelcast,它被用作二级缓存。
所以,这就是我所做的:
1.我没有创建EntityManagerFactory bean,而是创建了一个示例化DynamicTenantEntityManagerFactoryBeanLocalContainerEntityManagerFactoryBean

@Bean
 public LocalContainerEntityManagerFactoryBean entityManagerFactory() {

     DynamicTenantEntityManagerFactoryBean em = new DynamicTenantEntityManagerFactoryBean();
     em.setPackagesToScan(getPackagesToScan());
     em.setJpaProperties(additionalProperties());
     em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
     return em;

 }

getPackagesToScan()additionalProperties()是读取Sping Boot 在应用程序上下文上设置的参数的函数。

private String[] getPackagesToScan() {

    //Check if the defaults packagesToScan needs to be overrided
    String[] packagesToScan = dynamicTenantRoutingPropertiesConfig.getPackagesToScan();

    if (packagesToScan == null) {

        List<String> packageNames = EntityScanPackages.get(context).getPackageNames();
        if (packageNames.isEmpty() && AutoConfigurationPackages.has(context)) {
            packageNames = AutoConfigurationPackages.get(context);
        }

        packagesToScan = packageNames.toArray(new String[0]);

    }

    return packagesToScan;

}

private Properties additionalProperties() {
    
    //jpaProperties is an Autowired bean of type 
    //org.springframework.boot.autoconfigure.orm.jpa.JpaProperties

    final Properties properties = new Properties();
    properties.putAll(jpaProperties.getProperties());
    return properties;
}

DynamicTenantEntityManagerFactoryBean扩展LocalContainerEntityManagerFactoryBean并覆盖createNativeEntityManagerFactory(),使用与@tsvetelin-yakimov相同的技术

@Log4j2
@Configurable
public class DynamicTenantEntityManagerFactoryBean extends LocalContainerEntityManagerFactoryBean {

    @Autowired
    private ApplicationContext context;

    @PostConstruct
    public void postConstruct() {

        //Obtem a referencia para o Datasource
        AbstractDynamicTenantRoutingDataSource dataSource
                = context.getBean(AbstractDynamicTenantRoutingDataSource.class);

        //Define o Datasource
        this.setDataSource(dataSource);

    }

    @Override
    protected EntityManagerFactory createNativeEntityManagerFactory() throws PersistenceException {

        log.info("(DynamicTenantEntityManagerFactory): Iniciando Proxy...");
        final int hashcode = UUID.randomUUID().toString().hashCode();
        final Map<String, EntityManagerFactory> entityManagerFactoryMap = new HashMap<>();

        Object emf = Proxy.newProxyInstance(
                Thread.currentThread().getContextClassLoader(),
                new Class[]{SessionFactory.class},
                (proxy, method, args) -> {

                    if (method.getName().equals("hashCode")) {
                        return hashcode;
                    }

                    if (method.getName().equals("equals")) {
                        return proxy == args[0];
                    }

                    //Verifica se o DataSource esta preenchido
                    if (this.getDataSource() == null)
                        throw new PersistenceException("O DataSource não pode ser nulo");

                    //Verifica se ja temos o EntityManager para esse Datasource
                    String currentDriver = ((AbstractDynamicTenantRoutingDataSource) this.getDataSource()).resolveCurrentDriver();
                    EntityManagerFactory instance = entityManagerFactoryMap.get(currentDriver);

                    if (instance == null) {

                        //Obtem o dialeto
                        Dialect dialect = ((AbstractDynamicTenantRoutingDataSource) this.getDataSource()).resolveDialect(currentDriver);

                        log.info(
                                "(DynamicTenantEntityManagerFactory): Iniciando EntityManagerFactory para Driver {} Dialeto {}...",
                                currentDriver,
                                dialect
                        );

                        instance = this.createEntityManagerFactory(
                                dialect
                        );

                        entityManagerFactoryMap.put(
                                currentDriver,
                                instance
                        );

                    }

                    log.debug(
                            "(DynamicTenantEntityManagerFactory): Retornando EntityManagerFactory para Driver {}...",
                            currentDriver
                    );
                    return method.invoke(instance);

                }
        );

        log.info("(DynamicTenantEntityManagerFactory): Iniciando Proxy... Concluido!");
        return (SessionFactory) emf;

    }

    private EntityManagerFactory createEntityManagerFactory(Dialect dialect) {

        EntityManagerFactory factory = super.createNativeEntityManagerFactory();
        factory
                .getProperties()
                .put("hibernate.dialect", dialect.getClass().getName());

        return factory;

    }

}

相关问题