Web Service RESTful Spring Boot 2.0 com múltiplos bancos de dados

Hélio Márcio Filho
Dev.log
Published in
6 min readMar 26, 2018

O Spring MVC é uma ótima solução para o desenvolvimento de Back-End em Java. Trata-se de um framework bastante completo que fornece todos os recursos necessários para desenvolver Web Services, no entanto, por ser tão completo, muitas vezes a configuração inicial de um novo projeto tende a ser demorada. O Spring Boot apareceu para resolver este problema, pois ele permite criar novos projetos eliminando a maior parte das configurações que seriam necessárias se fosse utilizado o Spring Framework convencional.

Dentre muitas das facilidades que o Spring Boot oferece, uma delas é a capacidade de criar Web Services RESTful que acessam um número arbitrário de bancos de dados com poucas configurações, sejam eles de um mesmo distribuidor ou não.

A partir do Spring Boot versão 2.0.0 a tecnologia padrão responsável pelo pool de conexões deixou de ser o Tomcat Pool e passou a ser o HikariCP. Segundo as notas oficiais de lançamento da versão 2.0.0 o motivo da troca se deve ao fato do HikariCP possuir melhor desempenho e por muitos dos usuário preferirem esta solução. Mais detalhes nas notas oficiais de lançamento.

Porém, esta alteração mudou um pouco a forma como os Data Sources devem ser configurados para trabalhar com o novo HikariCP. Neste post será mostrado passo-a-passo como configurar um Web Service RESTful simples que se conecta a MySQL e H2 Database ao mesmo tempo.

Criando o projeto

Através do Spring Initializr será criado um novo projeto Spring Boot contendo as dependências: Web, JPA, MySQL e H2. As duas primeiras serão necessárias, pois ambas incluem respectivamente o Spring MVC e o Spring Data, dependências indispensáveis para o tipo de projeto deste exemplo, você pode ler mais sobre ambos aqui. As duas últimas dependências adicionam ao projeto os drivers de conexão JDBC dos bancos de dados que serão utilizados pela aplicação.

Gere o projeto e importe na sua IDE de preferência.

Criando os pacotes necessários

Ao criar um projeto com múltiplos bancos de dados é uma boa prática criar uma estrutura de pacotes individual para cada instância que se deseja conectar. Separar bem os componentes de cada banco de dados evita que o Spring e o próprio desenvolvedor se confundam na hora de injetar os repositórios de cada banco e torna mais fácil a localização das classes.

O Spring precisará mais a frente que o desenvolvedor informe onde procurar as classes de entidades e repositórios de cada conexão, para isso os dois tipos de classes de cada banco de dados serão mantidas em estruturas de pacotes distintas como abaixo:

src/main/java
- pacote.raiz
- db
- mysql
- model
- repository
- h2
- model
- repository

Vamos criar também o pacote config.db para armazenar as classes de configurações que criaremos a seguir.

Configurando Data Sources

MySQLDataSourceConfig.java:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef = "mysqlEntityManagerFactory",
transactionManagerRef = "mysqlTransactionManager",
basePackages = "br.com.devlog.springboot.db.mysql.repository")
public class MySQLDataSourceConfig {
@Primary
@Bean(name = "mysqlDataSource")
@ConfigurationProperties(prefix = "mysql.datasource")
public HikariDataSource mysqlDataSource() {
return DataSourceBuilder.create()
.type(HikariDataSource.class)
.build();
} @Primary
@Bean(name = "mysqlEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean mysqlEntityManagerFactory(EntityManagerFactoryBuilder builder, @Qualifier("mysqlDataSource") DataSource dataSource) {
return builder.dataSource(dataSource)
.packages("br.com.devlog.springboot.db.mysql.model")
.persistenceUnit("mysqlPU")
.build();
} @Primary
@Bean(name = "mysqlTransactionManager")
public PlatformTransactionManager mysqlTransactionManager(@Qualifier("mysqlEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}

Agora vamos analisar esta classe por partes:

@Configuration: Registra a classe como um componente Spring;

@EnableTransactionManagement: Habilita o gerenciamento de transações baseado em anotações;

@EnableJpaRepositories: Contém as configurações para o funcionamento dos repositórios Spring. Possui as propriedades:

  • entityManagerFactoryRef: Especifica o Bean que servirá como fábrica de EntityManager;
  • transactionManagerRef: Especifica o Bean provedor do gerenciador de transações (JpaTransactionManager);
  • basePackages: Recebe uma String que representa o pacote onde estarão as classes JpaRepository desta conexão. É possível substituir este atributo por basePackageClasses e fornecer como valor um Class<JpaRepository> evitando erros de digitação e o Spring será capaz de encontrar o nome do pacote através de introspecção.

@Primary: Caso haja múltiplos candidatos para a injeção de um Bean, aquele que possuir esta anotação será injetado.

@Bean(name = “mysqlDataSource”): Este método fornece um HikariDataSource que permite configurar conexões compatíveis com o HikariCP. Este objeto é utilizado posteriormente pelo Bean mysqlEntityManagerFactory. Este método possui a anotação @ConfigurationProperties(prefix = “mysql.datasource”) que possui um papel bastante importante, o Spring utilizará o valor de prefix para ler as configurações deste banco de dados no application.properties, ou seja, o que estiver especificado começando como mysql.datasource será entendido como configurações para este repositório.

@Qualifier: Diz ao Spring explicitamente através do nome que Bean injetar para satisfazer a dependência necessária.

@Bean(name = “mysqlEntityManagerFactory”): Este método informa ao Spring através do parâmetro de .packages() onde procurar pelas entidades para esta conexão, ou seja, onde encontrar as classes @Entity que representam tabelas do banco. Este método também pode receber um Class de algum objeto anotado com @Entity e o Spring irá encontrar o nome do pacote através de introspecção. O valor fornecido em .persistenceUnit() será utilizado para decidir que EntityManager injetar quando necessário.

@Bean(name = “mysqlTransactionManager”): Fornece um PlatformTransactionManager que será utilizado para controlar transações desta conexão.

De forma semelhante foi criada a classe de configuração do H2 Database:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef = "h2EntityManagerFactory",
transactionManagerRef = "h2TransactionManager",
basePackages = "br.com.devlog.springboot.db.h2.repository")
public class H2DataSourceConfig {
@Bean(name = "h2DataSource")
@ConfigurationProperties(prefix = "h2.datasource")
public HikariDataSource mysqlDataSource() {
return DataSourceBuilder.create()
.type(HikariDataSource.class)
.build();
}
@Bean(name = "h2EntityManagerFactory")
public LocalContainerEntityManagerFactoryBean h2EntityManagerFactory(EntityManagerFactoryBuilder builder, @Qualifier("h2DataSource") DataSource dataSource) {
return builder.dataSource(dataSource)
.packages("br.com.devlog.springboot.db.h2.model")
.persistenceUnit("h2PU")
.build();
}
@Bean(name = "h2TransactionManager")
public PlatformTransactionManager mysqlTransactionManager(@Qualifier("h2EntityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}

Ao declarar as configurações de cada banco de dados no application.properties utilizaremos os prefixos definidos em cada @ConfigurationProperties para separar as configurações de cada um: mysql.datasource e h2.datasource.

# MySQL with HikariCP
mysql.datasource.jdbc-url=jdbc:mysql://localhost/mysql_db_test?createDatabaseIfNotExist=true&useSSl=false
mysql.datasource.username=root
mysql.datasource.password=root
#H2 Database with HikariCP
h2.datasource.type=com.zaxxer.hikari.HikariDataSource
h2.datasource.jdbc-url=jdbc:h2:~/test/H2_DB_TEST
h2.datasource.driver-class-name=org.h2.Driver
h2.datasource.username=root
h2.datasource.password=root

Criando as entidades

Para este exemplo será criado duas entidades representando uma tabela de cada banco de dados. Importante ressaltar que as entidades devem ser criadas para cada banco no pacote especificado em .package() de cada configuração.

A estrutura do bancos de dados aos quais a aplicação se conecta é a seguinte:

Tabela ADDRESS no H2 Database e people no MySQL

Address.java

@Entity
@Table(catalog = "H2_DB_TEST", name = "ADDRESS")
public class Address implements Serializable {
private static final long serialVersionUID = -8239153122610067639L; @Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String city;
private String street;
private String number;

/* Getters, Setters, Equals and hashCode */
}

People.java

@Entity
@Table(name = "people")
public class People implements Serializable {
private static final long serialVersionUID = 1450467020855448316L; @Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Integer age;
/* Getters, Setters, Equals and hashCode */
}

Acessando os dados

Para acessar os dados é necessário criar uma interface que estende JpaRepository<T, ID> para cada uma das entidades.

AddressRepository.java

@Repository
public interface AddressRepository extends JpaRepository<Address, Long> { }

PeopleRepository.java

@Repository
public interface PeopleRepository extends JpaRepository<People, Long> { }

Finalmente, vamos implementar um controlador que responde a requisições HTTP e devolve os dados no formato JSON.

TestResource.java

@RestController
public class TestResource {
@Autowired
private PeopleRepository peopleRepository;
@Autowired
private AddressRepository addressRepository;
@GetMapping(value = "/h2")
public ResponseEntity<List<Address>> testH2() {
return ResponseEntity.ok(addressRepository.findAll());
}
@GetMapping(value = "/mysql")
public ResponseEntity<List<People>> testMysql() {
return ResponseEntity.ok(peopleRepository.findAll());
}
}

Ao realizar a chamada as informações de bancos diferentes são retornadas:

Dados sendo recebidos de bancos de dados diferentes

Estrutura final das classes do projeto:

Estrutura do projeto finalizado

Bônus: E se eu precisar injetar um EntityManager?

Você deve especificar explicitamente qual EntityManager deseja injetar informando o valor de .persistenceUnit() na propriedade unitName da anotação @PersistenceContext:

@PersistenceContext(unitName = "mysqlPU")
private EntityManager entityManagerMysql
@PersistenceContext(unitName = "h2PU")
private EntityManager entityManagerH2Database

Acesse o código-fonte no Github: https://github.com/heliomf-dev/spring-boot-multiple-databases

--

--