Spring JPA: múltiples bases de datos

1. Información general

En este tutorial, implementaremos una configuración Spring simple para un sistema Spring Data JPA con múltiples bases de datos .

2. Las Entidades

Primero, creemos dos entidades simples, cada una viviendo en una base de datos separada.

Aquí está la primera entidad " Usuario ":

package com.baeldung.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; }

Y la segunda entidad, " Producto ":

package com.baeldung.multipledb.model.product; @Entity @Table(schema = "products") public class Product { @Id private int id; private String name; private double price; }

Como puede ver, las dos entidades también se colocan en paquetes independientes ; esto será importante a medida que avancemos en la configuración.

3. Los repositorios de JPA

A continuación, echemos un vistazo a nuestros dos repositorios JPA: UserRepository :

package com.baeldung.multipledb.dao.user; public interface UserRepository extends JpaRepository { }

Y ProductRepository :

package com.baeldung.multipledb.dao.product; public interface ProductRepository extends JpaRepository { }

Note, nuevamente, cómo creamos estos dos repositorios en diferentes paquetes.

4. Configurar JPA con Java

A continuación, veamos la configuración real de Spring. Comenzaremos configurando dos clases de configuración: una para el Usuario y la otra para el Producto .

En cada una de estas clases de configuración, necesitaremos definir las siguientes interfaces para el usuario :

  • Fuente de datos
  • EntityManagerFactory ( userEntityManager )
  • TransactionManager ( userTransactionManager )

Comencemos mirando la configuración de usuario:

@Configuration @PropertySource({ "classpath:persistence-multiple-db.properties" }) @EnableJpaRepositories( basePackages = "com.baeldung.multipledb.dao.user", entityManagerFactoryRef = "userEntityManager", transactionManagerRef = "userTransactionManager" ) public class PersistenceUserConfiguration { @Autowired private Environment env; @Bean @Primary public LocalContainerEntityManagerFactoryBean userEntityManager() { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(userDataSource()); em.setPackagesToScan( new String[] { "com.baeldung.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; } }

Observe cómo estamos usando userTransactionManager como nuestro TransactionManager principal , anotando la definición del bean con @Primary . Eso es útil cuando vamos a inyectar implícita o explícitamente el administrador de transacciones sin especificar cuál por su nombre.

A continuación, analicemos PersistenceProductConfiguration , donde definimos beans similares:

@Configuration @PropertySource({ "classpath:persistence-multiple-db.properties" }) @EnableJpaRepositories( basePackages = "com.baeldung.multipledb.dao.product", entityManagerFactoryRef = "productEntityManager", transactionManagerRef = "productTransactionManager" ) public class PersistenceProductConfiguration { @Autowired private Environment env; @Bean public LocalContainerEntityManagerFactoryBean productEntityManager() { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(productDataSource()); em.setPackagesToScan( new String[] { "com.baeldung.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. Prueba simple

Finalmente, probemos nuestras configuraciones.

Intentaremos una prueba simple creando una instancia de cada entidad y nos aseguraremos de que esté creada, como en el siguiente ejemplo:

@RunWith(SpringRunner.class) @SpringBootTest @EnableTransactionManagement public class JpaMultipleDBIntegrationTest { @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. Varias bases de datos en Spring Boot

Spring Boot puede simplificar la configuración anterior.

De forma predeterminada, Spring Boot creará una instancia de su fuente de datos predeterminada con las propiedades de configuración con el prefijo spring.datasource. * :

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

Ahora queremos seguir usando la misma forma para configurar el segundo DataSource , pero con un espacio de nombres de propiedad diferente:

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

Debido a que queremos que la autoconfiguración de Spring Boot elija esas propiedades diferentes (y cree una instancia de dos fuentes de datos diferentes ), definiremos dos clases de configuración similares a las de las secciones anteriores:

@Configuration @PropertySource({"classpath:persistence-multiple-db-boot.properties"}) @EnableJpaRepositories( basePackages = "com.baeldung.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.baeldung.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 } 

Hemos definido las propiedades de la fuente de datos dentro de persistence-multiple-db-boot.properties de acuerdo con la convención de configuración automática de arranque.

La parte interesante es anotar el método de creación del bean de origen de datos con @ConfigurationProperties . Solo necesitamos especificar el prefijo de configuración correspondiente . Dentro de este método, estamos usando un DataSourceBuilder, y Spring Boot se encargará automáticamente del resto.

Pero, ¿cómo se inyectan las propiedades configuradas en la configuración de DataSource ?

Al llamar al método build () en DataSourceBuilder , llamará a su método bind () privado :

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

Este método privado realiza gran parte de la magia de la configuración automática, vinculando la configuración resuelta a la instancia de DataSource real :

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)); }

Aunque no tenemos que tocar nada de este código, sigue siendo útil saber qué está sucediendo bajo el capó de la configuración automática de Spring Boot.

Además de esto, la configuración de los beans de Transaction Manager y Entity Manager es la misma que la de la aplicación Spring estándar.

7. Conclusión

Este artículo fue una descripción general práctica de cómo configurar su proyecto Spring Data JPA para usar múltiples bases de datos.

La implementación completa de este artículo se puede encontrar en el proyecto GitHub: este es un proyecto basado en Maven, por lo que debería ser fácil de importar y ejecutar tal como está.