Esse tutorial te guiará no processo de configurar Spring Data para acessar múltiplos serviços SQL na PCF.

O que você irá construir

Você construirá uma aplicação que conecta com vários serviços MySQL na PCF.

Pre-req

Nesse tutorial eu estou usando a instalação PCF Dev.

Spring Boot com Spring Data

Spring Boot com Spring Data torna fácil acessar bancos de dados através de Repositórios e Spring Boot auto-configuration. Entretando, if a tua aplicação precisa acessar multiplos bancos de dados, isso não é algo provido pelas auto-configurações.

PCF Services

PCF oferece um marketplace de serviços que são provisionados por demanda. Para conectar uma aplicação spring em um serviço PCF existe Spring Cloud Connectors no qual usa os chamados Auto-Reconfiguration. Entretando, o Auto-Reconfiguration não funciona no caso em que você tem que conectar com vários serviços do mesmo tipo, por exemplo, vários serviços de banco de dados SQL. E para isso existe a necessidade de fazer uma configuração manual.

Configurando vários DataSources

Para conectar com multiplos DataSources na PCF nós precisaremos usar a Configuração Manual.

Primeiro adicione a seguinte dependencia ao pom.xml.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cloud-connectors</artifactId>
</dependency>

Configuração Java

Para conectar com vários DataSources nós precisamos criar uma nova classe que extends a AbstractCloudConfig do Spring Cloud Connectors e então criar dois @Beans, um para cada serviço PCF.

src/main/java/com/marcosbarbero/wd/pcf/multidatasources/config

import org.springframework.cloud.config.java.AbstractCloudConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class CloudConfig extends AbstractCloudConfig {

    @Primary
    @Bean(name = "first-db")
    public DataSource firstDataSource() {
        return connectionFactory().dataSource("first-db");
    }

    @Bean(name = "second-db")
    public DataSource secondDataSource() {
        return connectionFactory().dataSource("second-db");
    }
}

Java package

Crie um pacote java para cada DataSource, com dois pacotes internos: domain e repository

── com
    └── marcosbarbero
        └── wd
            └── pcf
                └── multidatasources
                    ├── first
                    │   ├── domain
                    │   └── repository
                    └── second
                        ├── domain
                        └── repository

Classes de configuração por Banco de dados

Como nós temos dois DataSources, é necessário ter uma classe de configuração por conexão. No nosso exemplo serão duas classes de configuração.

Para a primeira conexão com banco de dados crie a seguinte classe.

src/main/java/com/marcosbarbero/wd/pcf/multidatasources/config

import org.springframework.beans.factory.annotation.Qualifier;
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.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 javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

import static java.util.Collections.singletonMap;

@Configuration
@EnableJpaRepositories(
        entityManagerFactoryRef = "firstEntityManagerFactory",
        transactionManagerRef = "firstTransactionManager",
        basePackages = "com.marcosbarbero.wd.pcf.multidatasources.first.repository"
)
@EnableTransactionManagement
public class FirstDsConfig {

    @Primary
    @Bean(name = "firstEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean firstEntityManagerFactory(final EntityManagerFactoryBuilder builder,
                                                                            final @Qualifier("first-db") DataSource dataSource) {
        return builder
                .dataSource(dataSource)
                .packages("com.marcosbarbero.wd.pcf.multidatasources.first.domain")
                .persistenceUnit("firstDb")
                .properties(singletonMap("hibernate.hbm2ddl.auto", "create-drop"))
                .build();
    }

    @Primary
    @Bean(name = "firstTransactionManager")
    public PlatformTransactionManager firstTransactionManager(@Qualifier("firstEntityManagerFactory")
                                                              EntityManagerFactory firstEntityManagerFactory) {
        return new JpaTransactionManager(firstEntityManagerFactory);
    }
}

Para a segunda conexão com banco de dados, crie a seguinte classe.

src/main/java/com/marcosbarbero/wd/pcf/multidatasources/config

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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 javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

import static java.util.Collections.singletonMap;

@Configuration
@EnableJpaRepositories(
        entityManagerFactoryRef = "secondEntityManagerFactory",
        transactionManagerRef = "secondTransactionManager",
        basePackages = "com.marcosbarbero.wd.pcf.multidatasources.second.repository"
)
@EnableTransactionManagement
public class SecondDsConfig {

    @Bean(name = "secondEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean secondEntityManagerFactory(final EntityManagerFactoryBuilder builder,
                                                                             final @Qualifier("second-db") DataSource dataSource) {
        return builder
                .dataSource(dataSource)
                .packages("com.marcosbarbero.wd.pcf.multidatasources.second.domain")
                .persistenceUnit("secondDb")
                .properties(singletonMap("hibernate.hbm2ddl.auto", "create-drop"))
                .build();
    }

    @Bean(name = "secondTransactionManager")
    public PlatformTransactionManager secondTransactionManager(@Qualifier("secondEntityManagerFactory")
                                                               EntityManagerFactory secondEntityManagerFactory) {
        return new JpaTransactionManager(secondEntityManagerFactory);
    }
}

Para esse tutorial eu estou usando a propriedade hibernate.hbm2ddl.auto=create-drop somente para deixar as coisas mais fáceis, numa aplicação real esse valor não deve ser usado e deve-se usar algum método ou framework mais apropriado para inicialização dos dados e schemas dos banco de dados.

Modelo e Repositórios

Agora é hora de criar as classes model e repository que farão conexão com cada class de configuração descrita acima.

src/main/java/com/marcosbarbero/wd/pcf/multidatasources/first/domain

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "first")
public class First {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String text;

    public First(String text) {
        this.text = text;
    }

    public First() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    @Override
    public String toString() {
        return "First{" +
                "id=" + id +
                ", text='" + text + '\'' +
                '}';
    }
}

src/main/java/com/marcosbarbero/wd/pcf/multidatasources/first/repository

import com.marcosbarbero.wd.pcf.multidatasources.first.domain.First;

import org.springframework.data.jpa.repository.JpaRepository;

public interface FirstRepository extends JpaRepository<First, Long> {
}

src/main/java/com/marcosbarbero/wd/pcf/multidatasources/second/domain

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "second")
public class Second {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String text;

    public Second(String text) {
        this.text = text;
    }

    public Second() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    @Override
    public String toString() {
        return "Second{" +
                "id=" + id +
                ", text='" + text + '\'' +
                '}';
    }
}

src/main/java/com/marcosbarbero/wd/pcf/multidatasources/second/repository

import com.marcosbarbero.wd.pcf.multidatasources.second.domain.Second;

import org.springframework.data.jpa.repository.JpaRepository;

public interface SecondRepository extends JpaRepository<Second, Long> {
}

Executando

Para testar a aplicação eu adicionei o seguinte código na classe principal do projeto.

src/main/java/com/marcosbarbero/wd/pcf/multidatasources

import com.marcosbarbero.wd.pcf.multidatasources.first.domain.First;
import com.marcosbarbero.wd.pcf.multidatasources.first.repository.FirstRepository;
import com.marcosbarbero.wd.pcf.multidatasources.second.domain.Second;
import com.marcosbarbero.wd.pcf.multidatasources.second.repository.SecondRepository;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application implements CommandLineRunner {

    private static final Logger logger = LoggerFactory.getLogger(Application.class);

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Autowired
    private FirstRepository firstRepository;

    @Autowired
    private SecondRepository secondRepository;

    @Override
    public void run(String... args) throws Exception {
        First firstSaved = this.firstRepository.save(new First("first database"));
        Second secondSaved = this.secondRepository.save(new Second("second database"));

        logger.info(firstSaved.toString());
        logger.info(secondSaved.toString());
    }
}

Deploying to PCF

Para fazer o deploy da aplicação na PCF eu estou usando um arquivo manifest.yml.

applications:
- name: multiple-db
  memory: 512MB
  instance: 1
  path: ./target/your-jar-name.jar
  services:
   - first-db
   - second-db

Para esse exemplo eu criei dois serviços p-mysql chamados first-db e second-db.

Build

$ ./mvnw clean package

Deploy

$ cf push

Quando terminar o deploy da aplicação, ela irá imprimir a seguinte saída:

First{id=1, text='first database'}
Second{id=1, text='second database'}

Summary

Parabéns! Você criou uma aplicação Spring Boot que conecta com vários bancos de dados na PCF usando Spring Data.

Nota de rodapé

  • O código desse tutorial pode ser encontrado no github