Add multi-datasource module: two databases with per-database DataSource, Flyway, EntityManagerFactory and transaction manager on Boot 4.1
Working setup plus one small application per way of getting it wrong (no @Primary, @Primary only, url vs jdbc-url, wrong repository package, Flyway auto-configuration), plain vs named @Transactional, and ChainedTransactionManager with a failing commit. Transcripts are written by the tests. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Uu7q8vPeREyT4218EJPzz1
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
# Two databases, each with its own DataSource, Flyway, EntityManagerFactory and transaction manager
|
||||
|
||||
|
||||
--- the two DataSources (one pool each, configured separately) ---
|
||||
customersDataSource pool=customers-pool max=4 url=jdbc:h2:mem:customers primary=true
|
||||
ordersDataSource pool=orders-pool max=8 url=jdbc:h2:mem:orders primary=false
|
||||
|
||||
--- Flyway ran once per database, each with its own history ---
|
||||
customers flyway_schema_history: [1 customer]
|
||||
orders flyway_schema_history: [1 purchase order, 2 index customer]
|
||||
|
||||
--- what each EntityManagerFactory manages ---
|
||||
EntityManagerFactory beans: [customersEntityManagerFactory, ordersEntityManagerFactory]
|
||||
customersEntityManagerFactory entities=[Customer]
|
||||
ordersEntityManagerFactory entities=[PurchaseOrder]
|
||||
|
||||
--- transaction managers ---
|
||||
beans: [customersTransactionManager, ordersTransactionManager]
|
||||
customersTransactionManager primary=true
|
||||
ordersTransactionManager primary=false
|
||||
|
||||
--- save one customer and one order through the two repositories ---
|
||||
customers database tables: [CUSTOMER, flyway_schema_history]
|
||||
orders database tables: [PURCHASE_ORDER, flyway_schema_history]
|
||||
rows: customers.customer=1 orders.purchase_order=1
|
||||
@@ -0,0 +1,4 @@
|
||||
# Two DataSource beans, none marked @Primary
|
||||
|
||||
startup failed: BeanCreationException -> NoSuchBeanDefinitionException
|
||||
root cause: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'entityManagerFactory' available
|
||||
@@ -0,0 +1,8 @@
|
||||
# One @Primary DataSource and nothing else: it starts, and it writes to the wrong database
|
||||
|
||||
application started: yes
|
||||
EntityManagerFactory beans: [entityManagerFactory]
|
||||
customers database tables: [CUSTOMER, PURCHASE_ORDER]
|
||||
orders database tables: []
|
||||
rows in the customers database: customer=1 purchase_order=1
|
||||
hibernate.hbm2ddl.auto on that factory: create-drop
|
||||
@@ -0,0 +1,4 @@
|
||||
# DataSourceBuilder.create().build() bound to app.datasource.customers.url
|
||||
|
||||
startup failed: BeanCreationException -> BeanInstantiationException -> IllegalArgumentException
|
||||
root cause: java.lang.IllegalArgumentException: dataSource or dataSourceClassName or jdbcUrl is required.
|
||||
@@ -0,0 +1,6 @@
|
||||
# A hand-built EntityManagerFactory that sets no Hibernate properties of its own
|
||||
|
||||
hibernate.physical_naming_strategy = org.hibernate.boot.model.naming.PhysicalNamingStrategySnakeCaseImpl
|
||||
hibernate.implicit_naming_strategy = org.springframework.boot.hibernate.SpringImplicitNamingStrategy
|
||||
hibernate.hbm2ddl.auto = null
|
||||
saved a Customer whose field is createdAt into a column created_at: id=1
|
||||
@@ -0,0 +1,25 @@
|
||||
# Spring Boot's Flyway auto-configuration when there are two databases
|
||||
|
||||
|
||||
--- A. nothing configured: Flyway targets the @Primary DataSource and scans classpath:db/migration ---
|
||||
startup failed: BeanCreationException -> BeanCreationException -> FlywayException
|
||||
root cause: org.flywaydb.core.api.FlywayException: Found more than one migration with version 1
|
||||
Offenders:
|
||||
-> <multi-datasource>/target/classes/db/migration/customers/V1__customer.sql (SQL)
|
||||
-> <multi-datasource>/target/classes/db/migration/orders/V1__purchase_order.sql (SQL)
|
||||
|
||||
--- B. spring.flyway.locations=classpath:db/migration/customers ---
|
||||
startup failed: BeanCreationException -> PersistenceException -> SchemaManagementException
|
||||
root cause: org.hibernate.tool.schema.spi.SchemaManagementException: Schema validation: missing table [purchase_order]
|
||||
|
||||
application started (Hibernate validation switched off so the databases can be inspected)
|
||||
customers database tables: [CUSTOMER, flyway_schema_history]
|
||||
orders database tables: []
|
||||
|
||||
--- C. as B, plus one hand-made Flyway bean for the orders database ---
|
||||
startup failed: BeanCreationException -> PersistenceException -> SchemaManagementException
|
||||
root cause: org.hibernate.tool.schema.spi.SchemaManagementException: Schema validation: missing table [customer]
|
||||
|
||||
application started (Hibernate validation switched off so the databases can be inspected)
|
||||
customers database tables: []
|
||||
orders database tables: [PURCHASE_ORDER, flyway_schema_history]
|
||||
@@ -0,0 +1,4 @@
|
||||
# A repository for Customer scanned against the orders EntityManagerFactory
|
||||
|
||||
startup failed: BeanCreationException -> IllegalArgumentException
|
||||
root cause: java.lang.IllegalArgumentException: Not a managed type: class com.ankurm.multids.customers.Customer
|
||||
@@ -0,0 +1,10 @@
|
||||
# A service method that writes to both databases, then throws
|
||||
|
||||
|
||||
--- @Transactional (no name): Spring picks the @Primary manager, customersTransactionManager ---
|
||||
thrown: java.lang.IllegalStateException: boom after both writes
|
||||
rows after the rollback: customers.customer=0 orders.purchase_order=1
|
||||
|
||||
--- @Transactional("ordersTransactionManager"): the other database is now the one that rolls back ---
|
||||
thrown: java.lang.IllegalStateException: boom after both writes
|
||||
rows after the rollback: customers.customer=1 orders.purchase_order=0
|
||||
@@ -0,0 +1,13 @@
|
||||
# ChainedTransactionManager over the customers and orders managers
|
||||
|
||||
|
||||
--- 1. business exception after both writes: both roll back ---
|
||||
rows: customers.customer=0 orders.purchase_order=0
|
||||
|
||||
--- 2. the orders database refuses to commit; orders is LAST in the list ---
|
||||
thrown: org.springframework.transaction.HeuristicCompletionException (outcome: rolled back)
|
||||
rows: customers.customer=0 orders.purchase_order=0
|
||||
|
||||
--- 3. the orders database refuses to commit; orders is FIRST in the list ---
|
||||
thrown: org.springframework.transaction.HeuristicCompletionException (outcome: mixed)
|
||||
rows: customers.customer=1 orders.purchase_order=0
|
||||
@@ -0,0 +1,11 @@
|
||||
# Is org.springframework.data.transaction.ChainedTransactionManager still shipped, and is it deprecated?
|
||||
|
||||
jar: spring-data-commons-4.1.1.jar
|
||||
class: org/springframework/data/transaction/ChainedTransactionManager.class
|
||||
|
||||
--- javap -v: class-level annotations ---
|
||||
RuntimeVisibleAnnotations:
|
||||
0: #245()
|
||||
java.lang.Deprecated
|
||||
--- javap -v: Deprecated attribute ---
|
||||
Deprecated: true
|
||||
@@ -0,0 +1,6 @@
|
||||
# javac on a class carrying two @EnableJpaRepositories (scripts/facts/RepeatedEnable.java)
|
||||
|
||||
RepeatedEnable.java:7: error: EnableJpaRepositories is not a repeatable annotation interface
|
||||
@EnableJpaRepositories(basePackages = "a.orders", entityManagerFactoryRef = "ordersEntityManagerFactory")
|
||||
^
|
||||
1 error
|
||||
@@ -0,0 +1,9 @@
|
||||
# What does EntityManagerFactoryBuilder.Builder.packages(Class...) do with the classes it is given?
|
||||
|
||||
jar: spring-boot-jpa-4.1.1.jar
|
||||
|
||||
--- javap -c: the calls made inside packages(java.lang.Class<?>...) ---
|
||||
invokespecial #34 // Method java/util/HashSet."<init>":()V
|
||||
invokestatic #35 // Method org/springframework/util/ClassUtils.getPackageName:(Ljava/lang/Class;)Ljava/lang/String;
|
||||
invokeinterface #41, 2 // InterfaceMethod java/util/Set.add:(Ljava/lang/Object;)Z
|
||||
invokestatic #47 // Method org/springframework/util/StringUtils.toStringArray:(Ljava/util/Collection;)[Ljava/lang/String;
|
||||
Reference in New Issue
Block a user