Spring JPA - несколько баз данных

Spring JPA - несколько баз данных

1. обзор

В этом руководстве мы реализуем простую конфигурацию Spring дляSpring Data JPA system with multiple databases.

Дальнейшее чтение:

Spring Data JPA - производные методы удаления

Узнайте, как определить методы Spring Data deleteBy и removeBy

Read more

Конфигурирование источника данных программно в Spring Boot

Узнайте, как программно настроить Spring Boot DataSource, тем самым обойдя алгоритм автоматической настройки Spring Boot DataSource.

Read more

2. Сущности

Во-первых, давайте создадим две простые сущности, каждая из которых находится в отдельной базе данных.

Вот первая сущность «User»:

package com.example.multipledb.model.user;

@Entity
@Table(schema = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    private String name;

    @Column(unique = true, nullable = false)
    private String email;

    private int age;
}

И вторая сущность - «Product»:

package com.example.multipledb.model.product;

@Entity
@Table(schema = "products")
public class Product {

    @Id
    private int id;

    private String name;

    private double price;
}

Как видите,the two entities are also placed in independent packages - это будет важно при переходе к настройке.

Дальнейшее чтение:

Spring Data JPA - производные методы удаления

Узнайте, как определить методы Spring Data deleteBy и removeBy

Read more

Конфигурирование источника данных программно в Spring Boot

Узнайте, как программно настроить Spring Boot DataSource, тем самым обойдя алгоритм автоматической настройки Spring Boot DataSource.

Read more

3. Репозитории JPA

Далее - давайте взглянем на два наших репозитория JPA -UserRepository:

package com.example.multipledb.dao.user;

public interface UserRepository
  extends JpaRepository { }

ИProductRepository:

package com.example.multipledb.dao.product;

public interface ProductRepository
  extends JpaRepository { }

Обратите внимание, снова, как мы создали эти два репозитория в разных пакетах.

4. Настроить JPA с Java

Далее - перейдем к реальной конфигурации Spring. We’ll start by setting up 2 configuration classes – one for the User and the other for the Product.

В каждом из этих классов конфигурации нам нужно будет определить следующие интерфейсы дляUser:

  • Источник данных

  • EntityManagerFactory (userEntityManager)

  • TransactionManager (userTransactionManager)

Начнем с просмотра конфигурации пользователя:

@Configuration
@PropertySource({ "classpath:persistence-multiple-db.properties" })
@EnableJpaRepositories(
    basePackages = "com.example.multipledb.dao.user",
    entityManagerFactoryRef = "userEntityManager",
    transactionManagerRef = "userTransactionManager"
)
public class UserConfig {
    @Autowired
    private Environment env;

    @Bean
    @Primary
    public LocalContainerEntityManagerFactoryBean userEntityManager() {
        LocalContainerEntityManagerFactoryBean em
          = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(userDataSource());
        em.setPackagesToScan(
          new String[] { "com.example.multipledb.model.user" });

        HibernateJpaVendorAdapter vendorAdapter
          = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        HashMap properties = new HashMap<>();
        properties.put("hibernate.hbm2ddl.auto",
          env.getProperty("hibernate.hbm2ddl.auto"));
        properties.put("hibernate.dialect",
          env.getProperty("hibernate.dialect"));
        em.setJpaPropertyMap(properties);

        return em;
    }

    @Primary
    @Bean
    public DataSource userDataSource() {

        DriverManagerDataSource dataSource
          = new DriverManagerDataSource();
        dataSource.setDriverClassName(
          env.getProperty("jdbc.driverClassName"));
        dataSource.setUrl(env.getProperty("user.jdbc.url"));
        dataSource.setUsername(env.getProperty("jdbc.user"));
        dataSource.setPassword(env.getProperty("jdbc.pass"));

        return dataSource;
    }

    @Primary
    @Bean
    public PlatformTransactionManager userTransactionManager() {

        JpaTransactionManager transactionManager
          = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(
          userEntityManager().getObject());
        return transactionManager;
    }
}

Обратите внимание, как мы используемuserTransactionManager в качествеPrimary TransactionManager - путем аннотирования определения компонента с помощью@Primary. Это полезно всякий раз, когда мы собираемся неявно или явно внедрить диспетчер транзакций, не указывая, какой из них по имени.

Затем давайте обсудимProductConfig - где мы определяем похожие бины:

@Configuration
@PropertySource({ "classpath:persistence-multiple-db.properties" })
@EnableJpaRepositories(
    basePackages = "com.example.multipledb.dao.product",
    entityManagerFactoryRef = "productEntityManager",
    transactionManagerRef = "productTransactionManager"
)
public class ProductConfig {
    @Autowired
    private Environment env;

    @Bean
    public LocalContainerEntityManagerFactoryBean productEntityManager() {
        LocalContainerEntityManagerFactoryBean em
          = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(productDataSource());
        em.setPackagesToScan(
          new String[] { "com.example.multipledb.model.product" });

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        HashMap properties = new HashMap<>();
        properties.put("hibernate.hbm2ddl.auto",
          env.getProperty("hibernate.hbm2ddl.auto"));
        properties.put("hibernate.dialect",
          env.getProperty("hibernate.dialect"));
        em.setJpaPropertyMap(properties);

        return em;
    }

    @Bean
    public DataSource productDataSource() {

        DriverManagerDataSource dataSource
          = new DriverManagerDataSource();
        dataSource.setDriverClassName(
          env.getProperty("jdbc.driverClassName"));
        dataSource.setUrl(env.getProperty("product.jdbc.url"));
        dataSource.setUsername(env.getProperty("jdbc.user"));
        dataSource.setPassword(env.getProperty("jdbc.pass"));

        return dataSource;
    }

    @Bean
    public PlatformTransactionManager productTransactionManager() {

        JpaTransactionManager transactionManager
          = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(
          productEntityManager().getObject());
        return transactionManager;
    }
}

5. Простой тест

Наконец, давайте протестируем наши конфигурации.

Мы попробуем выполнить простой тест, создав экземпляр каждой сущности и удостоверившись, что он создан - как в следующем примере:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { UserConfig.class, ProductConfig.class })
@TransactionConfiguration
public class JPAMultipleDBTest {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private ProductRepository productRepository;

    @Test
    @Transactional("userTransactionManager")
    public void whenCreatingUser_thenCreated() {
        User user = new User();
        user.setName("John");
        user.setEmail("[email protected]");
        user.setAge(20);
        user = userRepository.save(user);

        assertNotNull(userRepository.findOne(user.getId()));
    }

    @Test
    @Transactional("userTransactionManager")
    public void whenCreatingUsersWithSameEmail_thenRollback() {
        User user1 = new User();
        user1.setName("John");
        user1.setEmail("[email protected]");
        user1.setAge(20);
        user1 = userRepository.save(user1);
        assertNotNull(userRepository.findOne(user1.getId()));

        User user2 = new User();
        user2.setName("Tom");
        user2.setEmail("[email protected]");
        user2.setAge(10);
        try {
            user2 = userRepository.save(user2);
        } catch (DataIntegrityViolationException e) {
        }

        assertNull(userRepository.findOne(user2.getId()));
    }

    @Test
    @Transactional("productTransactionManager")
    public void whenCreatingProduct_thenCreated() {
        Product product = new Product();
        product.setName("Book");
        product.setId(2);
        product.setPrice(20);
        product = productRepository.save(product);

        assertNotNull(productRepository.findOne(product.getId()));
    }
}

6. Несколько баз данных в Spring Boot

Spring Boot может упростить настройку выше.

По умолчаниюSpring Boot will instantiate its default DataSource with the configuration properties prefixed by spring.datasource.*:

spring.datasource.jdbcUrl = [url]
spring.datasource.username = [username]
spring.datasource.password = [password]

Теперь мы хотим продолжать использовать тот же способ дляconfigure the second DataSource, but with a different property namespace:

spring.second-datasource.jdbcUrl = [url]
spring.second-datasource.username = [username]
spring.second-datasource.password = [password]

Поскольку мы хотим, чтобы автоконфигурация Spring Boot подбирала эти разные свойства (и фактически создавала экземпляры двух разныхDataSources), мы определим 2 класса конфигурации, аналогичные тем, которые были в предыдущих разделах:

@Configuration
@PropertySource({"classpath:persistence-multiple-db-boot.properties"})
@EnableJpaRepositories(
  basePackages = "com.example.multipledb.dao.user",
  entityManagerFactoryRef = "userEntityManager",
  transactionManagerRef = "userTransactionManager")
public class PersistenceUserAutoConfiguration {

    @Primary
    @Bean
    @ConfigurationProperties(prefix="spring.datasource")
    public DataSource userDataSource() {
        return DataSourceBuilder.create().build();
    }
    // userEntityManager bean

    // userTransactionManager bean
}
@Configuration
@PropertySource({"classpath:persistence-multiple-db-boot.properties"})
@EnableJpaRepositories(
  basePackages = "com.example.multipledb.dao.product",
  entityManagerFactoryRef = "productEntityManager",
  transactionManagerRef = "productTransactionManager")
public class PersistenceProductAutoConfiguration {

    @Bean
    @ConfigurationProperties(prefix="spring.second-datasource")
    public DataSource productDataSource() {
        return DataSourceBuilder.create().build();
    }

    // productEntityManager bean

    // productTransactionManager bean
}

Мы определили свойства источника данных внутриpersistence-multiple-db-boot.properties в соответствии с соглашением об автоматической настройке загрузки.

Интересная часть:annotating the data source bean creation method with*@ConfigurationProperties*. Нам просто нужно указать соответствующий префикс конфигурации.. Внутри этого метода мы используемDataSourceBuilder,, и Spring Boot автоматически позаботится о отдых.

Но как на самом деле сконфигурированные свойства вводятся в конфигурациюDataSource?

При вызове методаbuild() дляDataSourceBuilder он вызывает его закрытый методbind():

public T build() {
    Class type = getType();
    DataSource result = BeanUtils.instantiateClass(type);
    maybeGetDriverClassName();
    bind(result);
    return (T) result;
}

Этот частный метод выполняет большую часть магии автоконфигурации, привязывая разрешенную конфигурацию к фактическому экземпляруDataSource:

private void bind(DataSource result) {
    ConfigurationPropertySource source = new MapConfigurationPropertySource(this.properties);
    ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
    aliases.addAliases("url", "jdbc-url");
    aliases.addAliases("username", "user");
    Binder binder = new Binder(source.withAliases(aliases));
    binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(result));
}

Хотя нам не нужно трогать этот код самостоятельно, все же полезно знать, что происходит под капотом автоконфигурации Spring Boot.

Кроме того, конфигурация компонентов Transaction Manager и Entity Manager такая же, как и в стандартном приложении Spring.

7. Заключение

Эта статья была практическим обзором того, как настроить проект Spring Data JPA для использования нескольких баз данных.

full implementation в этой статье можно найти вthe GitHub project - это проект на основе Maven, поэтому его должно быть легко импортировать и запускать как есть.