Register the default dataSource bean without dataSource configuration - #16458
Conversation
HibernateDatastoreSpringInitializer only included the default connection source in the data sources it registers beans for when the configuration had a non-empty dataSource block. An application without one still got a working default data source from the datastore, but no dataSource bean, so nothing could inject it by name or by type. The default connection source is now always included, as the datastore always creates it. The connection sources registrar now leaves a dataSource name alone that is already in use, also by a singleton, so the data source given to configureForDataSource stays the dataSource bean. Fixes #16455
|
|
||
| Set<String> dataSourceNames = new HashSet<String>() | ||
| // The datastore always creates the default connection source, whether or not it is configured | ||
| Set<String> dataSourceNames = [ConnectionSource.DEFAULT] as Set<String> |
There was a problem hiding this comment.
This is now a LinkedHashSet instead of a HashSet. It's a minor detail, but probably worth covering since you're changing the specific type of this.dataSources.
There was a problem hiding this comment.
Good point. [ConnectionSource.DEFAULT] as Set<String> gives a LinkedHashSet, which is what the dataSources field initialiser and the config == null branch already produced. Only the branch for a non-null configuration used a HashSet. The declared type of dataSources stays Set<String>.
With insertion order, the default connection source now always comes first, followed by the additional data sources in the order the configuration returns the dataSources map. Before, the order was whatever HashSet gave. The names are only iterated to register the dataSource_<name>, sessionFactory_<name> and transactionManager_<name> beans, so nothing depends on the order.
I've added a feature to HibernateDatastoreSpringInitializerSpec in both modules that checks the contents of dataSources with no configuration, with only dataSource, with only dataSources, and with both, and that the default connection source comes first.
|
The change from grails-data-hibernate5/grails-plugin/src/main/groovy/grails/orm/bootstrap/HibernateDatastoreSpringInitializer.groovy |
| } | ||
| ---- | ||
|
|
||
| With GORM for Hibernate, the default data source is always available as the `dataSource` bean, to inject by name or by type, even when the application has no `dataSource` block. Without configuration it connects to the in-memory H2 database `jdbc:h2:mem:grailsDB`. |
There was a problem hiding this comment.
This is only true when gorm is applied, yes? How would you disable the datasource? Is there no way after this PR?
There was a problem hiding this comment.
Yes, only when GORM for Hibernate (grails-data-hibernate7 or grails-data-hibernate5) is applied. Without it, DataSourceGrailsPlugin still registers a dataSource bean only when the application configures one, so nothing changes there. I've reworded the sentence to make that clearer.
As for disabling it: there was no way to do that before this PR either. HibernateDatastore always creates the default connection source, configured or not. In the issue's reproduction, an application without a dataSource block has a working HikariDataSource for jdbc:h2:mem:grailsDB behind hibernateDatastore.connectionSources.defaultConnectionSource, and GORM uses it. Before, that data source just wasn't registered as a bean. This PR only registers the data source GORM already creates and uses. It doesn't add one.
Without the H2 driver on the classpath, this default data source can't connect. That doesn't change with this PR, and the guide now says it needs the H2 driver. I checked with H2 excluded from the test runtime classpath and no dataSource block, before and after this PR, with the same results:
- without
hibernate.dialect, startup fails whenhibernateDatastoreis created ("Unable to determine Dialect without JDBC metadata"), because Hibernate needs a connection to detect the dialect - with
hibernate.dialectset, the application starts, and the first real query fails withClassNotFoundException: org.h2.Driver
The only difference is that the dataSource bean now exists in the second case. Creating it doesn't open a connection, so it can't make startup fail.
An application that defines its own dataSource bean keeps it. The registrar leaves a dataSource name that is already in use alone, and with this PR that includes singletons. The only way to not have a default data source is to not apply GORM for Hibernate.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## 8.0.x #16458 +/- ##
==================================================
+ Coverage 58.9274% 59.2788% +0.3513%
- Complexity 24440 24770 +330
==================================================
Files 2174 2170 -4
Lines 107366 108118 +752
Branches 19557 19707 +150
==================================================
+ Hits 63268 64091 +823
+ Misses 35235 35151 -84
- Partials 8863 8876 +13 🚀 New features to boost your workflow:
|
The data source names are now an insertion-ordered set, so the default connection source always comes first. Cover that and the names for each combination of dataSource and dataSources configuration. The guide now says that the dataSource bean is always present only when GORM for Hibernate is applied, and that the default H2 data source requires the H2 driver on the classpath.
This comment has been minimized.
This comment has been minimized.
jdaugherty
left a comment
There was a problem hiding this comment.
Let's be explicit on the type if we're going to change it?
|
@matrei for some reason my comments were removed when submitting them pending. 2 concerns here:
|
Build the data source names with an explicit LinkedHashSet instead of relying on the Set coercion, and pin both the set type and the full order of the names in the specs. The guide now says how to run without the default data source: exclude the hibernate plugin with grails.plugin.excludes. None of its beans are registered then, and a dataSource bean exists only when the application has a dataSource block.
|
Going to go ahead and merge this, let's iterate if you disagree with the changes. |
Fixes #16455
With GORM for Hibernate, an application without a
dataSourceblock got nodataSourcebean and noDataSourcebean at all, although the datastore created and used a default data source (jdbc:h2:mem:grailsDB). AnydataSourceblock, even one that only repeats a default, made the bean appear.HibernateDatastoreSpringInitializer.configureDataSourcesonly added the default connection source to the namesHibernateDatastoreConnectionSourcesRegistrarregisters beans for when thedataSourcemap was non-empty. It now always includes it, as theconfig == nullbranch already did, sinceHibernateDatastorealways creates the default connection source. AdditionaldataSourcesare added as before.Always including the default exposed a second problem.
configureForDataSource(DataSource)registers the given data source as adataSourcesingleton before the registrar runs, and the registrar only checked for an existing bean definition, so it replaced the given data source with its own. The registrar now skips a data source name that is already in use (BeanDefinitionRegistry.isBeanNameInUse), which also covers singletons and aliases. Before this change, the same replacement happened wheneverconfigureForDataSourcewas combined with adataSourceblock.The change is applied to both
grails-data-hibernate7andgrails-data-hibernate5, which had the same code.The DataSource section of the guide now says that the default data source is always available as the
dataSourcebean, also without adataSourceblock.Tests
In both Hibernate modules:
HibernateDatastoreSpringInitializerSpec:dataSourceis the onlyDataSourcebean, it is the datastore's default data source, and it connects tojdbc:h2:mem:grailsDBdataSources.booksconfigured, bothdataSourceanddataSource_booksare registeredconfigureForDataSourceremains thedataSourcebeanHibernateDatastoreConnectionSourcesRegistrarSpec: adataSourceregistered beforehand as a bean definition or as a singleton is keptWithout the fix, the first two initializer features fail (checked with Hibernate 7). With the old
containsBeanDefinitioncheck in the registrar, theconfigureForDataSourcefeature fails in both modules, and the singleton registrar feature fails (checked with Hibernate 7).