web-dev-qa-db-ja.com

同じリポジトリとモデルクラスを使用する複数のデータソースでSpringBoot?

次のように実行できるSpringBootバージョン1.5アプリケーションを実行する必要があります。オブジェクトを作成し、両方のデータソースに永続化しようとします(例:Postgresqlのtest_book_1とtest_book_2という名前の2つのデータベース)。

異なるデータベース(Aはtest_book_1に、Bはtest_book_2に移動)に格納できる2つの異なるオブジェクト(作成者:A、ブック:B)で機能する例を見つけました。これは良い例ですが、私が望んでいたものではありません。 別々のオブジェクトを異なるデータソースに保存する

2つのカスタムJPADatabaseConfigurationを定義する必要があり、同じリポジトリとドメインクラスを管理するようにそれらを構成する必要があるという考えが浮かびました。ただし、SpringはJPAリポジトリーに注入する修飾子として2番目のクラスのみを使用します(両方の構成が同じクラスを指している場合、2番目のクラスがオーバーライドできることを理解しています)。

問題は、必要なデータソースから正しいBean(BookRepository)をいつ注入する必要があるかをSpringに通知する方法です(私はオブジェクトを2番目のデータソースだけでなく、両方のデータソースに永続化するため)。

上記のリンク例から変更されたコードは次のとおりです。

Postgresqlに1つ、Mysqlに1つではなく、Postgresqlに2つのデータベースを作成するように変更されたapplication.propertiesファイル。

server.port=8082
# -----------------------
# POSTGRESQL DATABASE CONFIGURATION
# -----------------------
    spring.postgresql.datasource.url=jdbc:postgresql://localhost:5432/test_book_db
spring.postgresql.datasource.username=petauser
spring.postgresql.datasource.password=petapasswd
spring.postgresql.datasource.driver-class-name=org.postgresql.Driver

# ------------------------------
# POSTGRESQL 1 DATABASE CONFIGURATION
# ------------------------------

   spring.mysql.datasource.url=jdbc:postgresql://localhost:5432/test_author_db
spring.mysql.datasource.username=petauser
spring.mysql.datasource.password=petapasswd
spring.mysql.datasource.driver-class-name=org.postgresql.Driver

パッケージ:com.roufid.tutorial.configurationクラスAPostgresqlConfiguration

package com.roufid.tutorial.configuration;

import Java.io.IOException;
import Java.util.HashMap;
import Java.util.Map;
import Java.util.Properties;
import Java.util.stream.Collectors;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.roufid.tutorial.entity.postgresql.Book;

/**
 * Spring configuration of the "PostgreSQL" database.
 *
 * @author Radouane ROUFID.
 *
 */
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "postgresqlEntityManager",
        transactionManagerRef = "postgresqlTransactionManager",
        basePackages = "com.roufid.tutorial.dao.postgresql"
)
public class APostgresqlConfiguration {

    /**
     * PostgreSQL datasource definition.
     *
     * @return datasource.
     */
    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.postgresql.datasource")
    public DataSource postgresqlDataSource() {
        return DataSourceBuilder
                .create()
                .build();
    }

    /**
     * Entity manager definition.
     *
     * @param builder an EntityManagerFactoryBuilder.
     * @return LocalContainerEntityManagerFactoryBean.
     */
    @Primary
    @Bean(name = "postgresqlEntityManager")
    public LocalContainerEntityManagerFactoryBean postgresqlEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(postgresqlDataSource())
                .properties(hibernateProperties())
                .packages(Book.class)
                .persistenceUnit("postgresqlPU")
                .build();
    }

    @Primary
    @Bean(name = "postgresqlTransactionManager")
    public PlatformTransactionManager postgresqlTransactionManager(@Qualifier("postgresqlEntityManager") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }

    private Map<String, Object> hibernateProperties() {

        Resource resource = new ClassPathResource("hibernate.properties");

        try {
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            return properties.entrySet().stream()
                    .collect(Collectors.toMap(
                            e -> e.getKey().toString(),
                            e -> e.getValue())
                    );
        } catch (IOException e) {
            return new HashMap<String, Object>();
        }
    }
}

パッケージ:com.roufid.tutorial.configurationクラスMysqlConfiguration

package com.roufid.tutorial.configuration;

import Java.io.IOException;
import Java.util.HashMap;
import Java.util.Map;
import Java.util.Properties;
import Java.util.stream.Collectors;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.roufid.tutorial.entity.mysql.Author;
import com.roufid.tutorial.entity.postgresql.Book;

/**
 * Spring configuration of the "mysql" database.
 *
 * @author Radouane ROUFID.
 *
 */
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "mysqlEntityManager",
        transactionManagerRef = "mysqlTransactionManager",
        basePackages = "com.roufid.tutorial.dao.postgresql"
)
public class MysqlConfiguration {

    /**
     * MySQL datasource definition.
     *
     * @return datasource.
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.mysql.datasource")
    public DataSource mysqlDataSource() {
        return DataSourceBuilder
                .create()
                .build();
    }

    /**
     * Entity manager definition.
     *
     * @param builder an EntityManagerFactoryBuilder.
     * @return LocalContainerEntityManagerFactoryBean.
     */
    @Bean(name = "mysqlEntityManager")
    public LocalContainerEntityManagerFactoryBean mysqlEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(mysqlDataSource())
                .properties(hibernateProperties())
                .packages(Book.class)
                .persistenceUnit("mysqlPU")
                .build();
    }

    /**
     * @param entityManagerFactory
     * @return
     */
    @Bean(name = "mysqlTransactionManager")
    public PlatformTransactionManager mysqlTransactionManager(@Qualifier("mysqlEntityManager") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }

    private Map<String, Object> hibernateProperties() {

        Resource resource = new ClassPathResource("hibernate.properties");
    }
}    try {
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            return properties.entrySet().stream()
                    .collect(Collectors.toMap(
                            e -> e.getKey().toString(),
                            e -> e.getValue())
                    );
        } catch (IOException e) {
            return new HashMap<String, Object>();
        }
    }
}

パッケージcom.roufid.tutorial.dao.postgresqlクラスBookRepository

package com.roufid.tutorial.dao.postgresql;

import org.springframework.data.repository.CrudRepository;

import com.roufid.tutorial.entity.postgresql.Book;

/**
 * Book repository.
 * 
 * @author Radouane ROUFID.
 *
 */
public interface BookRepository extends CrudRepository<Book, Long> {

}

パッケージcom.roufid.tutorial.entity.postgresqlクラスブック

package com.roufid.tutorial.entity.postgresql;

import Java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "BOOK")
public class Book implements Serializable {

    private static final long serialVersionUID = -9019470250770543773L;

    @Id
    private Long id;

    @Column
    private String name;

    @Column
    private Long authorId;

    ...
    // Setters, Getters

}

また、MysqlConfigurationクラス(2番目のデータソース)のみを使用するBookRepositoryを挿入するためのテストクラス。

@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTest {
@Autowired
private BookRepository bookRepository;
@Before
public void init() {   
    Book book = new Book();
    book.setId(bookId);
    book.setName("Spring Boot Book");

    // How can it persist to the first datasource?  
    bookRepository.save(book);
}

}

9
Bằng Rikimaru

だから私は自分で答えを得たと思います(SpringJPAとHibernateのみに固執したいです)。これが私がしたことです 2つの異なるデータソースを備えたSpring Booth

最も重要なクラスは、2つのデータソース(Postgresqlでは2つのデータベース)を手動で作成するためのconfigクラスです。

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "sourceEntityManagerFactory",
        basePackages = "application"
)
public class PersistenceConfig {

    @Autowired
    private JpaVendorAdapter jpaVendorAdapter;

    private String databaseUrl = "jdbc:postgresql://localhost:5432/test_book_db";

    private String targetDatabaseUrl = "jdbc:postgresql://localhost:5432/test_author_db";

    private String username = "petauser";

    private String password = "petapasswd";

    private String driverClassName = "org.postgresql.Driver";

    private String dialect = "org.hibernate.dialect.PostgreSQLDialect";

    private String ddlAuto = "update";

    @Bean
    public EntityManager sourceEntityManager() {
        return sourceEntityManagerFactory().createEntityManager();
    }

    @Bean
    public EntityManager targetEntityManager() {
        return targetEntityManagerFactory().createEntityManager();
    }

    @Bean
    @Primary
    public EntityManagerFactory sourceEntityManagerFactory() {
        return createEntityManagerFactory("source", databaseUrl);
    }

    @Bean
    public EntityManagerFactory targetEntityManagerFactory() {
        return createEntityManagerFactory("target", targetDatabaseUrl);
    }

    @Bean(name = "transactionManager")
    @Primary
    public PlatformTransactionManager sourceTransactionManager() {
        return new JpaTransactionManager(sourceEntityManagerFactory());
    }

    @Bean
    public PlatformTransactionManager targetTransactionManager() {
        return new JpaTransactionManager(targetEntityManagerFactory());
    }

    private EntityManagerFactory createEntityManagerFactory(final String persistenceUnitName,
            final String databaseUrl) {
        final LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();

        final DriverManagerDataSource dataSource = new DriverManagerDataSource(databaseUrl, username, password);
        dataSource.setDriverClassName(driverClassName);
        entityManagerFactory.setDataSource(dataSource);

        entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter);
        entityManagerFactory.setPackagesToScan("application.domain");
        entityManagerFactory.setPersistenceUnitName(persistenceUnitName);

        final Properties properties = new Properties();
        properties.setProperty("hibernate.dialect", dialect);
        properties.setProperty("hibernate.hbm2ddl.auto", ddlAuto);
        entityManagerFactory.setJpaProperties(properties);

        entityManagerFactory.afterPropertiesSet();
        return entityManagerFactory.getObject();
    }

}

保存されたエンティティをソースデータベースからターゲットデータベースにコピーしたいためです。そこで、SpringJPAを使用してソースデータベースからオブジェクトを読み取りました

public interface StorageEntryRepository extends     CrudRepository<StorageEntry, Long> {

}

そして、Hibernateによってターゲットデータベースに永続化する前に、ターゲットデータベースに値(someValueには部分文字列 "Book"が含まれる)によって存在するエンティティをチェックするサービスクラスを作成しました(ここのStorageEntryは上記のリンク例のドメインクラスです) 。

@Service
@Transactional(rollbackFor = Exception.class)
public class StorageEntryService {

    @Autowired
    private StorageEntryRepository storageEntryRepository;

    @PersistenceContext(unitName = "target")
    private EntityManager targetEntityManager;

    public void save(StorageEntry storageEntry) throws Exception {

        // this.storageEntryRepository.save(storageEntry);

        // Load an stored entry from the source database
        StorageEntry storedEntry = this.storageEntryRepository.findOne(12L);                
        //this.storageEntryRepository.save(storageEntry);
        // Save also to a different database
        final Session targetHibernateSession = targetEntityManager.unwrap(Session.class);
        Criteria criteria = targetHibernateSession.createCriteria(StorageEntry.class);

        criteria.add(Restrictions.like("someValue", "%Book1%"));
        List<StorageEntry> storageEntries = criteria.list();

        if (storageEntries.isEmpty()) {
            targetEntityManager.merge(storedEntry);
            // No flush then nodata is saved in the different database
            targetHibernateSession.flush();
            System.out.println("Stored the new object to target database.");
        } else {
            System.out.println("Object already existed in target database.");
        }


    }
}

したがって、現在の作業アプリケーションから両方のJPAを使用でき、既存のオブジェクトを新しいデータベースに移行するには、構成クラスとサービスクラスを使用して別のアプリケーションを作成する必要があります。

4
Bằng Rikimaru

マルチテナンシーサポートが必要なようです。

これにはSpringベースのソリューションがあります

CurrentTenantIdentifierResolverインターフェースを実装する必要があります

public String resolveCurrentTenantIdentifier()

そして拡張

AbstractDataSourceBasedMultiTenantConnectionProviderImpl

テナントのデータソースを返す

もっと見る ここ

2
StanislavL