Skip to content

Add support for R2DBC #25065

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed

Conversation

mp911de
Copy link
Member

@mp911de mp911de commented May 13, 2020

This commit introduces support for R2DBC
("Reactive Relational Database Connectivity") with custom
ConnectionFactory implementations, a functional DatabaseClient for
SQL execution, transaction management, a bind marker abstraction
database initialization utilities and exception translation.

Open issues:

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label May 13, 2020
@jhoeller jhoeller self-assigned this May 13, 2020
@jhoeller jhoeller added in: data Issues in data modules (jdbc, orm, oxm, tx) type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels May 13, 2020
@jhoeller jhoeller added this to the 5.3 M1 milestone May 13, 2020
@rstoyanchev
Copy link
Contributor

Documentation (should it go side by side with the JDBC documentation)

The Data Access section would be a logical place for it. A short section could also be added under WebFlux mostly for discoverability, similar to how Spring MVC has a section about REST clients which are otherwise in other sections.

@jhoeller
Copy link
Contributor

jhoeller commented May 19, 2020

A few notes from a first pass of mine:

  • I'm not sure we really need the connectionfactory.lookup package, its JDBC equivalent was mostly introduced for JPA setup purposes.
  • We might be able to trim the connectionfactory handles and also the connectionfactory.init stuff a bit, not needing some of the flexibility that the JDBC equivalents have there.
  • I wonder whether the exception translation abstraction in the support package even needs to be an abstraction here since we effectively only really need the exception subclass check.
  • Since connectionfactory is a bit of a mouthful for a package name and support is so light, I propose to collapse those two into a unified support package, simply with connectionfactory root merging into support root and connectionfactory.init becoming support.init.

@sbrannen
Copy link
Member

Discuss reuse of ScriptUtils and friends

Aside from missing original @author tags (which is a very minor documentation detail), that is indeed a lot of copy-n-paste there. A bug fix in one of those will require a bug fix in the parallel implementation. (note: we used to have the same problem when some of that support also lived duplicated in spring-test) So perhaps we should look into a way to avoid the duplication. Though, we don't currently have a general purpose "data access" module in which that could reside. Plus, changing the packages would be a breaking change for existing applications. And... the standard implementation of course doesn't use reactive types. So coming up with a general purpose base implementation might be a big hurdle.

@jhoeller
Copy link
Contributor

jhoeller commented May 19, 2020

Specifically about the connection handling business in connectionfactory, I don't see the need for the ConnectionHandle abstraction here, could simply always hold a Connection directly in ConnectionHolder. Its JDBC equivalent was primarily introduced for JPA connection borrowing.

Also, I wonder whether we need EE-style decoration of a transaction-aware ConnectionFactory here. This primarily makes sense for plain R2DBC access code that would transparently work with declarative transaction management; the question being whether that is a common scenario. For code that is Spring-aware to begin with, ConnectionFactoryUtils.getConnection does the job as well.

@jhoeller
Copy link
Contributor

A specific note on R2dbcTransactionManager: We got a similar JdbcTransactionManager in the jdbc.support package in 5.3 now, including exception translation which DataSourceTransactionManager doesn't have (and cannot depend on for package cycle reasons between jdbc.datasource and jdbc.support), so placing the R2DBC equivalent in r2dbc.support would be well aligned.

Talking about exception translation there, JdbcTransactionManager actually propagates a translated DataAccessException as-is, similar to JpaTransactionManager flush exceptions. R2dbcTransactionManager should follow that pattern, not wrapping a translated DataAccessException into a TransactionSystemException but rather only wrapping non-translated exceptions that way.

@mp911de
Copy link
Member Author

mp911de commented May 19, 2020

Thanks for having a look. Here are my thoughts on the mentioned items:

  • connectionfactory.lookup: We've seen feature requests for a routing ConnectionFactory implementation (see Add support for AbstractRoutingConnectionFactory spring-data-r2dbc#98), so following the JDBC design seemed the most appropriate choice to remain consistent.
  • Most R2DBC drivers use R2dbcException subclasses to distinguish between exceptions. Falling back to SQLState-based exception translation seemed a viable alternative for those which do not follow the class-based approach.
  • ScriptUtils and named parameter handling: Really, a lot of this code duplicates what's provided by spring-jdbc already. I would like to aim for an approach, where at least the common parts (script splitting, named parameter identification) would be available from a org.springframework.dao-like package so that we can remove the duplications. Clearly, execution-specifics such as methods returning a Mono cannot be unified.
  • transaction-aware ConnectionFactory: Currently, we cannot properly answer this question. There are too little clients available. We've been in touch with users that use the R2DBC API directly and so a transaction-aware variant of ConnectionFactory provides value. Thinking a few steps ahead, R2DBC libraries would not be required to provide a Spring-specific customization on their side if a supplied ConnectionFactory already works according to transaction rules.
  • R2dbcTransactionManager/ConnectionFactoryTransactionManager: We can move things around and align these with JdbcTransactionManager. We should follow at least the exception wrapping/translation rules.
  • Regarding the connectionfactory package, how about reducing its name to just connection following the JMS design?

In general, I'm happy to reduce the API surface and move things around for the parts that lived in JDBC only to cater for JPA reasons.

@jhoeller
Copy link
Contributor

Shortening r2dbc.connectionfactory to r2dbc.connection sounds like a fine option as well. I wonder what to do with the very light r2dbc.support package though, we could also try to get rid of that one completely, moving R2dbcExceptionTranslator to r2dbc.connection (where it fits usage-wise).

As for the SQL state exception parsing, I'm rather strongly aiming to avoid this here. Over there in JDBC land, it is an old and hard-coded arrangement of very specific vendor codes, largely outlived by the JDBC 4 exception subclass support for a long time already. Since this is just about throwing more specific exception subclasses, it's not too bad if a particular driver does not distinguish much at all. I do not want our code to make up for a lack of sophistication in the drivers here... again. The appropriate target for a user to request finer-grained exception translation is the driver vendor.

Collapsing R2dbcExceptionTranslator into an interface plus single default implementation might therefore be worthwhile since other benefits of the equivalent JDBC separation - JDBC 4 runtime dependencies (for the SQLException subclasses) vs JDBC 2/3 compatibility, or the customizable error code files - don't really show here and are also outlived on the JDBC side already. For R2DBC, we could arguably have a single default implementation checking the exception subclasses and leave the rest up to the drivers. Even further, we could turn it into a translation utility like with our JmsUtils.convertJmsAccessException and Hibernate SessionFactoryUtils.convertHibernateAccessException methods, with no SPI interface and no configuration options for users, just doing internal exception subclass adaptation.

Our JDBC support was very maintenance intensive over the years, in particular due to its many vendor-specific checks (also for parameter type determination, value extraction, stored procedure handling). We have a clean plate to start with here, let's keep it as streamlined as possible.

@jhoeller
Copy link
Contributor

Some further notes about the connection package:

  • SingleConnectionConnectionFactory could be named SingleConnectionFactory like with JMS.
  • SmartConnectionFactory is based on the old SmartDataSource.shouldClose idea which only really makes sense if avoiding a proxy is essential, e.g. for code that does a hard cast to OracleConnection. Since such hard casts are not expected here and common connection proxying always suppresses close calls anyway, I don't think we need this mechanism with R2DBC.
  • ConnectionProxy predates the JDBC 4 unwrap mechanism, and since R2DBC comes with a similar unwrap mechanism out of the box, I see no need for preserving this separate mechanism.
  • Going even further, I wonder whether we need a single connection factory for R2DBC at all. With JDBC, this is mostly for testing, and even there most people use a DriverManagerDataSource or a simple pool instead. I'm inclined to drop SingleConnectionConnectionFactory completely, along with SmartConnectionFactory and the DelegatingConnectionFactory base class.
  • Since I also expect ConnectionHandle and SimpleConnectionHandle to go away, the resulting connection package would be significantly lighter: just containing ConnectionFactoryUtils, ConnectionHolder, R2dbcTransactionManager, and potentially TransactionAwareConnectionFactoryProxy (incorporating the delegation parts directly). This would easily allow for putting the R2dbcExceptionTranslator mechanism into the same package, in particular when condensed to an interface plus single default implementation, or when turning it into a utility method in ConnectionFactoryUtils (as suggested above).

@mp911de
Copy link
Member Author

mp911de commented May 22, 2020

I applied the discussed changes. Let me know if there's anything missing.

I also experimented with the method naming of DatabaseClient. Switching execute(String sql) to sql(String sql) aligns nicely here. Renaming fetch(…) to execute(…) causes that the code becomes less readable. Example:

databaseClient.sql().execute().all()
databaseClient.sql().execute().first()

The fetch variant feels more natural in this context:

Flux<Map<String, Object>> flux = databaseClient.sql(…).fetch().all();
Flux<Map<T, Object>> flux = databaseClient.sql(…).map(…).all();
Mono<Void> then = databaseClient.sql(…).then();

I will add the documentation bits early next week.

@mp911de mp911de force-pushed the spring-r2dbc branch 2 times, most recently from 86f65b2 to e888061 Compare May 26, 2020 06:18
@jhoeller
Copy link
Contributor

@mp911de This looks good to me so far, ready to get merged as a first cut.

@sbrannen Any immediate input on the connection.init package that we should address before the merge still? Seems a bit of a shame that we have to duplicate five exceptions there. Was there a specific need for diffentiating that much in the JDBC variant? I guess that's also related to the general question of ScriptUtils reuse...

@mp911de
Copy link
Member Author

mp911de commented May 27, 2020

FTR: I omitted the init package from the documentation for now until we're entirely happy with the init package.

@sbrannen
Copy link
Member

@sbrannen Any immediate input on the connection.init package that we should address before the merge still?

I'm reviewing it now and will get back to you.

Seems a bit of a shame that we have to duplicate five exceptions there. Was there a specific need for diffentiating that much in the JDBC variant?

Two of them were already in place since 3.0. When I reworked ScriptUtils, there was a need for two additional exceptions that I introduced in 4.0.3 along with the ScriptException base type to group them all together. So, it may seem like a lot, but I think it's reasonable, though indeed unfortunate that we have to duplicate all of them.

I guess that's also related to the general question of ScriptUtils reuse...

Yes, indeed: directly related.

@sbrannen
Copy link
Member

@sbrannen Any immediate input on the connection.init package that we should address before the merge still?

I don't think there's anything urgent that needs to be addressed before the initial merge.

However, I reviewed the status quo and came up with some topics for discussion.

  • DatabasePopulatorUtils.execute(DatabasePopulator, ConnectionFactory) could likely be a default method in DatabasePopulator -- no need to copy that utils pattern from spring-jdbc.
  • ResourceDatabasePopulator should support multiple commentPrefixes like in the JDBC variant.
  • Javadoc for ResourceDatabasePopulator.setBlockCommentEndDelimiter(String) uses improper escaping with {@code } element.
  • DatabasePopulator was originally only intended for "populating" a database (i.e., creating it), but the Javadoc points out how the use cases changed over time: "Strategy used to populate, initialize, or clean up a database." Thus, if we are creating something completely new for R2DBC, it may be a good idea to come up with a better name than DatabasePopulator, since cleanup often entails the opposite of "population".
  • I think it would be worthwhile to take a shot at extracting the common code from ScriptUtils into something that resides in spring-tx so that spring-jdbc and spring-r2dbc could delegate to or extend that common functionality. The challenge, however, likely lies in the fact that ScriptUtils in spring-jdbc is closely coupled to the ScriptException hierarchy in org.springframework.jdbc.datasource.init, but perhaps we can come up with a way around that. Let's brainstorm.
  • I also think we should investigate extracting common functionality from ResourceDatabasePopulator since the only differences between those two variants are the implementations of populate() and execute(). Maybe we need an abstraction that encapsulates the "resource-based configuration of scripts" since that is 90% of the code in those ResourceDatabasePopulator implementations -- then we could have "execution-specific" subtypes.

@jhoeller
Copy link
Contributor

jhoeller commented Jun 8, 2020

Let's merge this after the 5.2.7 release tomorrow (which unfortunately keeps us busier than expected), maybe on Wednesday this week? We may then follow up with refinements on master right afterwards.

@jhoeller
Copy link
Contributor

@mp911de Since we seem to have a conflict in the dependency declarations, it'd be great if you could rebase your branch and update to Arabba-SR4 in preparation for a merge to master... I'd also appreciate a quick final review pass over it from your side before Sam and I take over :-)

mp911de added 3 commits June 15, 2020 15:08
This commit introduces support for R2DBC
("Reactive Relational Database Connectivity") with custom
ConnectionFactory implementations, a functional DatabaseClient for
SQL execution, transaction management, a bind marker abstraction
database initialization utilities and exception translation.
Refactor DatabasePopulatorUtils.execute(…) into default method DatabasePopulator.populate(…). Add support for commentPrefixes in ResourceDatabasePopulator.
@sbrannen sbrannen self-assigned this Jun 15, 2020
Refactor DatabasePopulatorUtils.execute(…) into default method
DatabasePopulator.populate(…).
Add support for commentPrefixes in ResourceDatabasePopulator.

Consistently use code instead of literal in Javadoc for boolean
and null values.
@mp911de
Copy link
Member Author

mp911de commented Jun 15, 2020

Done. Rebased and ready for you to take over.

@sbrannen sbrannen closed this in aff601e Jun 17, 2020
@sbrannen
Copy link
Member

This first cut for the R2DBC support has been merged into master in time for Spring Framework 5.3 M1.

Kudos to @mp911de and @christophstrobl!

@sbrannen
Copy link
Member

The ScriptUtils topic will be addressed in #25275.

@mp911de mp911de deleted the spring-r2dbc branch June 18, 2020 12:39
kenny5he pushed a commit to kenny5he/spring-framework that referenced this pull request Jun 21, 2020
This commit introduces support for R2DBC ("Reactive Relational Database
Connectivity") with custom ConnectionFactory implementations, a
functional DatabaseClient for SQL execution, transaction management, a
bind marker abstraction database initialization utilities, and
exception translation.

Closes spring-projectsgh-25065
jhoeller added a commit that referenced this pull request Oct 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: data Issues in data modules (jdbc, orm, oxm, tx) type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants