Существование Spring Boot настолько упростило создание приложений, что разработчикам для подключении к базе данных уже даже не обязательно знать о существовании EntityManager
. Поэтому работа сразу с двумя БД может оказаться не простой задачей.
Так как для подключения сразу двух баз данных в одно SpringBoot приложение одной конфигурацией не обойтись, необходимо руками прописать все создаваемые бины. SpringBoot здесь не помощник, вспоминаем старый добрый Spring. Давайте разбираться.
Постановка задачи
По вымышленному ТЗ нам необходимо написать небольшое CRUD приложение для правительства РФ, которое анализирует судебные дела. Проблема заключается в том, что данные об адвокатах у нас лежат в одной базе, а данные о судьях в другой.
Наше приложение будет максимально простым:
- Две сущности. Два репозитория. Два REST контроллера;
- CRUD операции;
- Две базы данных: Postgres и H2;
Создание классов
Для начала создадим наши сущности это Judge
и Lawyer
, которые наследуют Person
.
Базовая сущность Person
выглядит следующим образом:
package dev.struchkov.example.multipledatabases.domain;
@Setter
@Getter
@MappedSuperclass
public class Person {
@Id
@GeneratedValue(generator = "UUID")
@GenericGenerator(
name = "UUID",
strategy = "org.hibernate.id.UUIDGenerator"
)
@Column(name = "id", updatable = false, nullable = false)
private UUID id;
@Column
private String firstName;
@Column
private String lastName;
}
Сущность Lawyer
выглядит аналогично сущности Judge
, за исключением того, что они лежат в разных пакетах. Сущности для одной БД должны лежать в одном пакете, а сущности для второй БД в другом пакете.
Теперь создадим JPA репозитории, которые тоже положим в разные пакеты.
package dev.struchkov.example.multipledatabases.repository.dbone;
@Repository
public interface JudgeRepository extends JpaRepository<Judge, UUID> {
}
И контроллеры для каждой сущности. Они могут лежать в одном пакете.
package dev.struchkov.example.multipledatabases.controller;
@RestController
@RequiredArgsConstructor
@RequestMapping("api/lawyer")
public class LawyerController {
private final LawyerRepository lawyerRepository;
@PostMapping
public ResponseEntity<Lawyer> create(@RequestBody Lawyer lawyer) {
return ResponseEntity.ok(lawyerRepository.save(lawyer));
}
@GetMapping("{id}")
public ResponseEntity<Lawyer> getById(@PathVariable UUID id) {
return ResponseEntity.ok(lawyerRepository.findById(id).orElseThrow());
}
@DeleteMapping("{id}")
public HttpStatus delete(@PathVariable UUID id) {
lawyerRepository.deleteById(id);
return HttpStatus.OK;
}
}
Конфигурации БД
Теперь дело за конфигурацией для баз данных. Сначала создаем конфигурацию для Judge
.
package dev.struchkov.example.multipledatabases.config;
@EnableJpaRepositories(
entityManagerFactoryRef = DatabaseOneConfig.ENTITY_MANAGER_FACTORY,
transactionManagerRef = DatabaseOneConfig.TRANSACTION_MANAGER,
basePackages = DatabaseOneConfig.JPA_REPOSITORY_PACKAGE
)
@Configuration
public class DatabaseOneConfig {
public static final String PROPERTY_PREFIX = "app.dbone.datasource";
public static final String JPA_REPOSITORY_PACKAGE = "dev.struchkov.example.multipledatabases.repository.dbone";
public static final String ENTITY_PACKAGE = "dev.struchkov.example.multipledatabases.domain.entity.dbone";
public static final String ENTITY_MANAGER_FACTORY = "oneEntityManagerFactory";
public static final String DATA_SOURCE = "oneDataSource";
public static final String DATABASE_PROPERTY = "oneDatabaseProperty";
public static final String TRANSACTION_MANAGER = "oneTransactionManager";
@Bean(DATABASE_PROPERTY)
@ConfigurationProperties(prefix = PROPERTY_PREFIX)
public DatabaseProperty appDatabaseProperty() {
return new DatabaseProperty();
}
@Bean(DATA_SOURCE)
public DataSource appDataSource(
@Qualifier(DATABASE_PROPERTY) DatabaseProperty databaseProperty
) {
return DataSourceBuilder
.create()
.username(databaseProperty.getUsername())
.password(databaseProperty.getPassword())
.url(databaseProperty.getUrl())
.driverClassName(databaseProperty.getClassDriver())
.build();
}
@Bean(ENTITY_MANAGER_FACTORY)
public LocalContainerEntityManagerFactoryBean appEntityManager(
@Qualifier(DATA_SOURCE) DataSource dataSource
) {
final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPersistenceUnitName(ENTITY_MANAGER_FACTORY);
em.setPackagesToScan(ENTITY_PACKAGE);
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
final HashMap<String, Object> properties = new HashMap<>();
properties.put("javax.persistence.validation.mode", "none");
properties.put("hibernate.hbm2ddl.auto", "update");
properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
em.setJpaPropertyMap(properties);
return em;
}
@Bean(TRANSACTION_MANAGER)
public PlatformTransactionManager sqlSessionTemplate(
@Qualifier(ENTITY_MANAGER_FACTORY) LocalContainerEntityManagerFactoryBean entityManager,
@Qualifier(DATA_SOURCE) DataSource dataSource
) {
final JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManager.getObject());
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
Чтобы работать с двумя базами нужно создать для каждой БД свой EntityManagerFactory
и TransactionManager
. А также указать, какие JPA репозитории и Entity относятся к этой БД.
В наших конфигурациях будут отличаться только строки 12-18 это переменные настройки:
PROPERTY_PREFIX
– Префикс для конфигурации в файлеapplication.property
JPA_REPOSITORY_PACKAGE
– Путь до пакета, где лежат JPA репозитории для данной БДENTITY_PACKAGE
– Путь до пакета, где лежат EntityENTITY_MANAGER_FACTORY
– Название бина EntityManagerDATA_SOURCE
– Название бина DataSourceDATABASE_PROPERTY
– Название бинаDatabaseProperty
TRANSACTION_MANAGER
– Название бинаTransactionManager
Названия для бинов нужны, чтобы спринг из двух вариантов выбирал правильный для конкретной БД.
DatabaseProperty
Первый бин DatabaseProperty
отвечает за передачу данных из файла aplication.property
для подключения к бд: пароль, логин, url и драйвер.
package dev.struchkov.example.multipledatabases.config;
@Getter
@Setter
public class DatabaseProperty {
private String url;
private String username;
private String password;
private String classDriver;
}
Создадим необходимые записи в aplication.property
. Не забываем про PROPERTY_PREFIX
.
app:
dbone:
datasource:
class-driver: org.h2.Driver
username: sa
password: password
url: jdbc:h2:mem:dbh2
DataSource
Теперь, когда у нас есть данные для подключения к БД создаем DataSource
. С помощью @Qualifier
указываем какой именно бин DatabaseProperty
нам нужен.
EntityManager
Далее нам нужен EntityManagerFactoryBean
.
- В 44 строке мы указываем наш
DataSource
. - В 45 строке устанавливаем название для
EntityManager
. - В 46 строке мы передаем путь до пакета, в котором у нас лежат Entities.
- А строки 49-53 позволяют задать проперти для Hibernate.
TransactionManager
Ну и последним шагом создаем бин PlatformTransactionManager
. Для работы ему нужны бины EntityManager
и Datasource
.
Вторая база данных
Для второй базы данных будут отличаться только параметры настройки.
package dev.struchkov.example.multipledatabases.config;
@EnableJpaRepositories(
entityManagerFactoryRef = DatabaseTwoConfig.ENTITY_MANAGER_FACTORY,
transactionManagerRef = DatabaseTwoConfig.TRANSACTION_MANAGER,
basePackages = DatabaseTwoConfig.JPA_REPOSITORY_PACKAGE
)
@Configuration
public class DatabaseTwoConfig {
public static final String PROPERTY_PREFIX = "app.dbtwo.datasource";
public static final String JPA_REPOSITORY_PACKAGE = "dev.struchkov.example.multipledatabases.repository.dbtwo";
public static final String ENTITY_PACKAGE = "dev.struchkov.example.multipledatabases.domain.entity.dbtwo";
public static final String ENTITY_MANAGER_FACTORY = "twoEntityManagerFactory";
public static final String DATA_SOURCE = "twoDataSource";
public static final String DATABASE_PROPERTY = "twoDatabaseProperty";
public static final String TRANSACTION_MANAGER = "twoTransactionManager";
}
Указываем для поиска другие пакеты и другие названия бинов, а также указываем другой префикс для передачи параметров подключения к БД.
Записываем эти параметры в application.yml
:
app:
dbone:
datasource:
class-driver: org.h2.Driver
username: sa
password: password
url: jdbc:h2:mem:dbh2
dbtwo:
datasource:
class-driver: org.postgresql.Driver
username: postgres
password: 12345
url: jdbc:postgresql://localhost:5432/dbpostgres
Резюмирую
Чтобы создать соединение сразу с несколькими базами данных, необходимо самостоятельно прописать необходимые бины для работы.