Spring Boot configura y usa dos fuentes de datos

12 minutos de lectura

avatar de usuario
juventus

¿Cómo puedo configurar y usar dos fuentes de datos?

Por ejemplo, esto es lo que tengo para la primera fuente de datos:

aplicación.propiedades

#first db
spring.datasource.url = [url]
spring.datasource.username = [username]
spring.datasource.password = [password]
spring.datasource.driverClassName = oracle.jdbc.OracleDriver

#second db ...

Clase de aplicación

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

como modifico application.properties agregar otra fuente de datos? ¿Cómo lo conecto automáticamente para que lo use un repositorio diferente?

avatar de usuario
K. Siva Prasad Reddy

Aquí tienes.

Agregue su archivo application.properties:

#first db
spring.datasource.url = [url]
spring.datasource.username = [username]
spring.datasource.password = [password]
spring.datasource.driverClassName = oracle.jdbc.OracleDriver

#second db ...
spring.secondDatasource.url = [url]
spring.secondDatasource.username = [username]
spring.secondDatasource.password = [password]
spring.secondDatasource.driverClassName = oracle.jdbc.OracleDriver

Agregue en cualquier clase anotada con @Configuration los siguientes métodos:

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

@Bean
@ConfigurationProperties(prefix="spring.secondDatasource")
public DataSource secondaryDataSource() {
    return DataSourceBuilder.create().build();
}

  • Echa un vistazo a baeldung.com/spring-data-jpa-multiple-databases que describe lo mismo que usted está buscando.

    – K. Siva Prasad Reddy

    21 de mayo de 2015 a las 2:34

  • A veces, es posible que deba asignar la fuente de datos, el administrador de transacciones y SqlSessionFactory como principal.

    – Dai Kaixian

    15 de diciembre de 2016 a las 10:37

  • @K. Siva Prasad Reddy Está bien, pero tengo 2 repositorios JPAR diferentes. ¿Cómo sabe Spring Boot qué fuente de datos usar? Cada JPARepository debería usar una base de datos diferente

    – Matley

    18 de marzo de 2019 a las 14:50

  • @Matley Esta publicación de blog javadevjournal.com/spring-boot/… podría ser lo que estás buscando.

    – K. Siva Prasad Reddy

    19 de marzo de 2019 a las 9:52

  • @K.SivaPrasadReddy para configurar múltiples fuentes de datos, ¿todas las bases de datos deben estar en el mismo servidor?

    – Krish

    2 de junio de 2019 a las 5:13

avatar de usuario
Surasin Tancharoen

Actualización 2022-05-29 con Spring Boot 1.5.8.RELEASE que debería funcionar con Spring Boot 2.x

La mayoría de las respuestas no proporcionan cómo usarlas (como fuente de datos y como transacción), solo cómo configurarlas.

Además, debe saber cómo comprometer/revertir transacciones de ambas fuentes de datos al mismo tiempo.

Puede ver el ejemplo ejecutable y alguna explicación en https://github.com/surasint/surasint-examples/tree/master/spring-boot-jdbi/10_spring-boot-dos-bases de datos (vea lo que puede probar en README.txt)

Copié un código aquí.

Primero tienes que configurar application.properties como esta

#Database
database1.datasource.url=jdbc:mysql://localhost/testdb
database1.datasource.username=root
database1.datasource.password=root
database1.datasource.driver-class-name=com.mysql.jdbc.Driver

database2.datasource.url=jdbc:mysql://localhost/testdb2
database2.datasource.username=root
database2.datasource.password=root
database2.datasource.driver-class-name=com.mysql.jdbc.Driver

Luego defínelos como proveedores (@Bean) así:

@Bean(name = "datasource1")
@ConfigurationProperties("database1.datasource")
@Primary
public DataSource dataSource(){
    return DataSourceBuilder.create().build();
}

@Bean(name = "datasource2")
@ConfigurationProperties("database2.datasource")
public DataSource dataSource2(){
    return DataSourceBuilder.create().build();
}

Tenga en cuenta que tengo @Bean(name="datasource1") y @Bean(name="datasource2")entonces puede usarlo cuando necesitemos una fuente de datos como @Qualifier("datasource1") y @Qualifier("datasource2") por ejemplo

@Qualifier("datasource1")
@Autowired
private DataSource dataSource;

Si le importa la transacción, debe definir DataSourceTransactionManager para ambos, así:

@Bean(name="tm1")
@Autowired
@Primary
DataSourceTransactionManager tm1(@Qualifier ("datasource1") DataSource datasource) {
    DataSourceTransactionManager txm  = new DataSourceTransactionManager(datasource);
    return txm;
}

@Bean(name="tm2")
@Autowired
DataSourceTransactionManager tm2(@Qualifier ("datasource2") DataSource datasource) {
    DataSourceTransactionManager txm  = new DataSourceTransactionManager(datasource);
    return txm;
}

Entonces puedes usarlo como

@Transactional //this will use the first datasource because it is @primary

o

@Transactional("tm2")

La parte más importante, de la que difícilmente encontrarás un ejemplo en ningún sitio: si desea un método para confirmar/deshacer transacciones de ambas bases de datos, necesita ChainedTransactionManager para tm1 y tm2, así:

@Bean(name = "chainedTransactionManager")
public ChainedTransactionManager getChainedTransactionManager(@Qualifier ("tm1") DataSourceTransactionManager tm1, @Qualifier ("tm2") DataSourceTransactionManager tm2){
    return new ChainedTransactionManager(tm1, tm2);
}

Para usarlo, agregue esta anotación en un método @Transactional(value=”chainedTransactionManager”) por ejemplo

@Transactional(value="chainedTransactionManager")
public void insertAll() {
    UserBean test = new UserBean();
    test.setUsername("username" + new Date().getTime());
    userDao.insert(test);

    userDao2.insert(test);
}

Esto debería ser suficiente. Ver ejemplo y detalle en el enlace de arriba.

  • Hola, @Surasin Tancharoen, estamos tratando de mantener dos fuentes de datos con los mismos datos para que, si una falla, la aplicación se ejecute en la otra fuente de datos. ¿Estará bien el enfoque anterior?

    – Arun Sudhakaran

    13 de mayo de 2020 a las 5:02

  • @ArunSudhakaran No. Esta solución no funcionará como respaldo. Si busca alta disponibilidad, la mayoría de las bases de datos ya tienen configuraciones para ejecutar varias bases de datos con una única IP virtual. prueba eso en su lugar.

    – Raja Anbazhagan

    8 de julio de 2021 a las 15:15

  • Enlace surasint.com/spring-boot-with-multiple-databases-example ya no funciona

    – Avec

    27/09/2021 a las 11:49

avatar de usuario
Faraj Farook

Referirse la documentación oficial


Crear más de una fuente de datos funciona igual que crear la primera. Es posible que desee marcar uno de ellos como @Primary si está utilizando la configuración automática predeterminada para JDBC o JPA (entonces ese será seleccionado por cualquier inyección de @Autowired).

@Bean
@Primary
@ConfigurationProperties(prefix="datasource.primary")
public DataSource primaryDataSource() {
    return DataSourceBuilder.create().build();
}

@Bean
@ConfigurationProperties(prefix="datasource.secondary")
public DataSource secondaryDataSource() {
    return DataSourceBuilder.create().build();
}

  • Gracias por el enlace a la documentación oficial sobre esto.

    – Manoj Shrestha

    10 sep 2021 a las 19:29

También tuve que configurar la conexión a 2 fuentes de datos desde la aplicación Spring Boot, y no fue fácil: la solución mencionada en el Documentación de Spring Boot no funcionó Después de una larga búsqueda en Internet, lo hice funcionar y la idea principal fue tomada de Este artículo y un montón de otros lugares.

La siguiente solución está escrita en kotlin y trabaja con Arranque de primavera 2.1.3 y Núcleo de hibernación 5.3.7. El problema principal era que no bastaba con configurar diferentes Fuente de datos configs, pero también era necesario configurar EntityManagerFactory y TransactionManager para ambas bases de datos.

Aquí está la configuración para la primera base de datos (Principal):

@Configuration
@EnableJpaRepositories(
    entityManagerFactoryRef = "firstDbEntityManagerFactory",
    transactionManagerRef = "firstDbTransactionManager",
    basePackages = ["org.path.to.firstDb.domain"]
)
@EnableTransactionManagement
class FirstDbConfig {

    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.firstDb")
    fun firstDbDataSource(): DataSource {
        return DataSourceBuilder.create().build()
    }

    @Primary
    @Bean(name = ["firstDbEntityManagerFactory"])
    fun firstDbEntityManagerFactory(
        builder: EntityManagerFactoryBuilder,
        @Qualifier("firstDbDataSource") dataSource: DataSource
    ): LocalContainerEntityManagerFactoryBean {
        return builder
            .dataSource(dataSource)
            .packages(SomeEntity::class.java)
            .persistenceUnit("firstDb")
            // Following is the optional configuration for naming strategy
            .properties(
                singletonMap(
                    "hibernate.naming.physical-strategy",
                    "org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl"
                )
            )
            .build()
    }

    @Primary
    @Bean(name = ["firstDbTransactionManager"])
    fun firstDbTransactionManager(
        @Qualifier("firstDbEntityManagerFactory") firstDbEntityManagerFactory: EntityManagerFactory
    ): PlatformTransactionManager {
        return JpaTransactionManager(firstDbEntityManagerFactory)
    }
}

Y esta es la configuración para la segunda base de datos:

@Configuration
@EnableJpaRepositories(
    entityManagerFactoryRef = "secondDbEntityManagerFactory",
    transactionManagerRef = "secondDbTransactionManager",
    basePackages = ["org.path.to.secondDb.domain"]
)
@EnableTransactionManagement
class SecondDbConfig {

    @Bean
    @ConfigurationProperties("spring.datasource.secondDb")
    fun secondDbDataSource(): DataSource {
        return DataSourceBuilder.create().build()
    }

    @Bean(name = ["secondDbEntityManagerFactory"])
    fun secondDbEntityManagerFactory(
        builder: EntityManagerFactoryBuilder,
        @Qualifier("secondDbDataSource") dataSource: DataSource
    ): LocalContainerEntityManagerFactoryBean {
        return builder
            .dataSource(dataSource)
            .packages(EntityFromSecondDb::class.java)
            .persistenceUnit("secondDb")
            .build()
    }

    @Bean(name = ["secondDbTransactionManager"])
    fun secondDbTransactionManager(
        @Qualifier("secondDbEntityManagerFactory") secondDbEntityManagerFactory: EntityManagerFactory
    ): PlatformTransactionManager {
        return JpaTransactionManager(secondDbEntityManagerFactory)
    }
}

Las propiedades de las fuentes de datos son así:

spring.datasource.firstDb.jdbc-url=
spring.datasource.firstDb.username=
spring.datasource.firstDb.password=

spring.datasource.secondDb.jdbc-url=
spring.datasource.secondDb.username=
spring.datasource.secondDb.password=

El problema con las propiedades era que tenía que definir jdbc-url en vez de URL porque de lo contrario tenía una excepción.

PD
También es posible que tenga diferentes esquemas de nombres en sus bases de datos, como fue mi caso. Dado que Hibernate 5 no es compatible con todos los esquemas de nombres anteriores, tuve que usar la solución de esta respuesta; tal vez también ayude a alguien.

avatar de usuario
Chandra Shekhar Goka

Aquí está la solución completa

#First Datasource (DB1)
db1.datasource.url: url
db1.datasource.username:user
db1.datasource.password:password

#Second Datasource (DB2)
db2.datasource.url:url
db2.datasource.username:user
db2.datasource.password:password

Dado que vamos a obtener acceso a dos bases de datos diferentes (db1, db2), debemos configurar cada fuente de datos por separado como:

public class DB1_DataSource {
@Autowired
private Environment env;
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean db1EntityManager() {
    LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
    em.setDataSource(db1Datasource());
    em.setPersistenceUnitName("db1EntityManager");
    HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
    em.setJpaVendorAdapter(vendorAdapter);
    HashMap<string, object=""> properties = new HashMap<>();
    properties.put("hibernate.dialect",
            env.getProperty("hibernate.dialect"));
    properties.put("hibernate.show-sql",
            env.getProperty("jdbc.show-sql"));
    em.setJpaPropertyMap(properties);
    return em;
}

@Primary
@Bean
public DataSource db1Datasource() {

    DriverManagerDataSource dataSource
            = new DriverManagerDataSource();
    dataSource.setDriverClassName(
            env.getProperty("jdbc.driver-class-name"));
    dataSource.setUrl(env.getProperty("db1.datasource.url"));
    dataSource.setUsername(env.getProperty("db1.datasource.username"));
    dataSource.setPassword(env.getProperty("db1.datasource.password"));

    return dataSource;
}

@Primary
@Bean
public PlatformTransactionManager db1TransactionManager() {

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

Segunda fuente de datos:

public class DB2_DataSource {

@Autowired
private Environment env;

@Bean
public LocalContainerEntityManagerFactoryBean db2EntityManager() {
    LocalContainerEntityManagerFactoryBean em
            = new LocalContainerEntityManagerFactoryBean();
    em.setDataSource(db2Datasource());
    em.setPersistenceUnitName("db2EntityManager");
    HibernateJpaVendorAdapter vendorAdapter
            = new HibernateJpaVendorAdapter();
    em.setJpaVendorAdapter(vendorAdapter);
    HashMap<string, object=""> properties = new HashMap<>();
    properties.put("hibernate.dialect",
            env.getProperty("hibernate.dialect"));
    properties.put("hibernate.show-sql",
            env.getProperty("jdbc.show-sql"));
    em.setJpaPropertyMap(properties);
    return em;
}

@Bean
public DataSource db2Datasource() {
    DriverManagerDataSource dataSource
            = new DriverManagerDataSource();
    dataSource.setDriverClassName(
            env.getProperty("jdbc.driver-class-name"));
    dataSource.setUrl(env.getProperty("db2.datasource.url"));
    dataSource.setUsername(env.getProperty("db2.datasource.username"));
    dataSource.setPassword(env.getProperty("db2.datasource.password"));

    return dataSource;
}

@Bean
public PlatformTransactionManager db2TransactionManager() {
    JpaTransactionManager transactionManager
            = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(
            db2EntityManager().getObject());
    return transactionManager;
}
}

Aquí puedes encontrar el ejemplo completo en mi blog:
Spring Boot con configuración de fuente de datos múltiple

  • este código no se ejecutará de la forma en que 2 anotaciones @primary funcionan juntas en la misma base de datos.

    – Rohit Chaurasiya

    24 de julio de 2020 a las 2:51

  • de hecho, pero este código menciona una parte importante con la configuración manual de propiedades en lugar de simple y llanamente return DataSourceBuilder.create().build(); que aparentemente no funciona, por lo tanto, mi voto a favor va aquí

    – soy_infame

    20 oct 2020 a las 15:38

avatar de usuario
Raju Ranjan

# Here '1stDB' is the database name
spring.datasource.url=jdbc:mysql://localhost/A
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver


# Here '2ndDB' is the database name
spring.second-datasourcee.url=jdbc:mysql://localhost/B
spring.second-datasource.username=root
spring.second-datasource.password=root
spring.second-datasource.driver-class-name=com.mysql.jdbc.Driver


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

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

  • este código no se ejecutará de la forma en que 2 anotaciones @primary funcionan juntas en la misma base de datos.

    – Rohit Chaurasiya

    24 de julio de 2020 a las 2:51

  • de hecho, pero este código menciona una parte importante con la configuración manual de propiedades en lugar de simple y llanamente return DataSourceBuilder.create().build(); que aparentemente no funciona, por lo tanto, mi voto a favor va aquí

    – soy_infame

    20 oct 2020 a las 15:38

avatar de usuario
Anil Konduru

Mi requisito era ligeramente diferente pero usaba dos fuentes de datos.

He usado dos fuentes de datos para las mismas entidades JPA del mismo paquete. Uno para ejecutar DDL en el inicio del servidor para crear/actualizar tablas y otro para DML en tiempo de ejecución.

La conexión DDL debe cerrarse después de que se ejecuten las declaraciones DDL, para evitar el uso posterior de previllages de superusuario en cualquier parte del código.

Propiedades

spring.datasource.url=jdbc:postgresql://Host:port
ddl.user=ddluser
ddl.password=ddlpassword
dml.user=dmluser
dml.password=dmlpassword
spring.datasource.driver-class-name=org.postgresql.Driver

Clases de configuración de origen de datos

// Primera clase de configuración para la fuente de datos DDL

  public class DatabaseDDLConfig {
        @Bean
        public LocalContainerEntityManagerFactoryBean ddlEntityManagerFactoryBean() {
            LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
            PersistenceProvider persistenceProvider = new 
            org.hibernate.jpa.HibernatePersistenceProvider();
            entityManagerFactoryBean.setDataSource(ddlDataSource());
            entityManagerFactoryBean.setPackagesToScan(new String[] { 
            "com.test.two.data.sources"});
            HibernateJpaVendorAdapter vendorAdapter = new 
            HibernateJpaVendorAdapter();
            entityManagerFactoryBean.setJpaVendorAdapter(vendorAdapter);
            HashMap<String, Object> properties = new HashMap<>();
            properties.put("hibernate.dialect", 
            "org.hibernate.dialect.PostgreSQLDialect");
            properties.put("hibernate.physical_naming_strategy", 
            "org.springframework.boot.orm.jpa.hibernate.
            SpringPhysicalNamingStrategy");
            properties.put("hibernate.implicit_naming_strategy", 
            "org.springframework.boot.orm.jpa.hibernate.
            SpringImplicitNamingStrategy");
            properties.put("hibernate.hbm2ddl.auto", "update");
            entityManagerFactoryBean.setJpaPropertyMap(properties);
            entityManagerFactoryBean.setPersistenceUnitName("ddl.config");
            entityManagerFactoryBean.setPersistenceProvider(persistenceProvider);
            return entityManagerFactoryBean;
        }


    @Bean
    public DataSource ddlDataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));
        dataSource.setUrl(env.getProperty("spring.datasource.url"));
        dataSource.setUsername(env.getProperty("ddl.user");
        dataSource.setPassword(env.getProperty("ddl.password"));
        return dataSource;
    }

    @Bean
    public PlatformTransactionManager ddlTransactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(ddlEntityManagerFactoryBean().getObject());
        return transactionManager;
    }
}

//2da clase de configuración para la fuente de datos DML

public class DatabaseDMLConfig {

    @Bean
    @Primary
    public LocalContainerEntityManagerFactoryBean dmlEntityManagerFactoryBean() {
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
        PersistenceProvider persistenceProvider = new org.hibernate.jpa.HibernatePersistenceProvider();
        entityManagerFactoryBean.setDataSource(dmlDataSource());
        entityManagerFactoryBean.setPackagesToScan(new String[] { "com.test.two.data.sources" });
        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        entityManagerFactoryBean.setJpaVendorAdapter(vendorAdapter);
        entityManagerFactoryBean.setJpaProperties(defineJpaProperties());
        entityManagerFactoryBean.setPersistenceUnitName("dml.config");
        entityManagerFactoryBean.setPersistenceProvider(persistenceProvider);
        return entityManagerFactoryBean;
    }

    @Bean
    @Primary
    public DataSource dmlDataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));
        dataSource.setUrl(envt.getProperty("spring.datasource.url"));
        dataSource.setUsername("dml.user");
        dataSource.setPassword("dml.password");
        return dataSource;
    }

    @Bean
    @Primary
    public PlatformTransactionManager dmlTransactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(dmlEntityManagerFactoryBean().getObject());
        return transactionManager;
    }


  }

//Uso de fuentes de datos DDL en el código.

public class DDLServiceAtStartup {

//Import persistence unit ddl.config for ddl purpose.

@PersistenceUnit(unitName = "ddl.config")
private EntityManagerFactory entityManagerFactory;

public void executeDDLQueries() throws ContentServiceSystemError {
    try {
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        entityManager.getTransaction().begin();
        entityManager.createNativeQuery("query to create/update table").executeUpdate();
        entityManager.flush();
        entityManager.getTransaction().commit();
        entityManager.close();

        //Close the ddl data source to avoid from further use in code.
        entityManagerFactory.close();
    } catch(Exception ex) {}
}

//Uso de la fuente de datos DML en el código.

public class DDLServiceAtStartup {
  @PersistenceUnit(unitName = "dml.config")
  private EntityManagerFactory entityManagerFactory;

  public void createRecord(User user) {
     userDao.save(user);
  }
}

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad