-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
HHH-15422 Pick up CurrentTenantIdentifierResolver and MultiTenantConnectionProvider from BeanContainer if not explicit set
#8697
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
Conversation
|
Could you review this also? @gavinking |
|
So we have not up until this point allowed
I dunno, WDYT @sebersole @scottmarlow @yrodiere? |
To others reading this: the conversation continued on the Jira, see https://hibernate.atlassian.net/browse/HHH-15422?focusedCommentId=116437 But IMO the main point here is that a
That's a good point, too much CDI reliance can be problematic in Jakarta application servers, or at least in WildFly. I would add that relying on CDI in very early phases of bootstrap is problematic for Quarkus as well, if we want to move more of the bootstrap to build time. Though this specific PR seems fine in that regard.
I kind of agree, but I don't think that's a healthy approach unless we have a clearly identified Jira issue and at least the beginning of a plan to solve that chicken/egg problem. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please redo this to use the mechanism described here:
https://jakarta.ee/specifications/persistence/3.2/jakarta-persistence-spec-3.2#entity-listeners
and also, redundantly, here:
https://jakarta.ee/specifications/persistence/3.2/jakarta-persistence-spec-3.2#a2999
These passages of the JPA spec describe how to enable injection into a persistence-managed object without having to make that object into a CDI bean.
This is actually what we should always be doing in this sort of situation.
hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java
Show resolved
Hide resolved
...-core/src/test/java/org/hibernate/orm/test/idgen/userdefined/UserDefinedGeneratorsTests.java
Show resolved
Hide resolved
hibernate-core/src/test/java/org/hibernate/orm/test/idgen/userdefined/SimpleBeanContainer.java
Show resolved
Hide resolved
...a/org/hibernate/engine/jdbc/connections/internal/MultiTenantConnectionProviderInitiator.java
Show resolved
Hide resolved
...a/org/hibernate/engine/jdbc/connections/internal/MultiTenantConnectionProviderInitiator.java
Show resolved
Hide resolved
|
To be clear, because there's apparently a lot of historical confusion on this point. There's three strong reasons why something might need to be a bean:
None of those motivations apply to the objects we're talking about here, and so they do not need to be beans. |
|
If you think about a |
I'm not sure if you've simply not read my comments, or if you've not understood them. But the whole point is that:
Please go and read those sections of the JPA spec which explain how to do it. (We even already have an implementation of that in Hibernate, by the way.)
Because that ties the lifecycle of Hibernate with the lifecycle of CDI and creates problems. |
|
So it turns out that my objections to this pull request actually boil down to only the following, which occurs in two places: @Override
public boolean useJpaCompliantCreation() {
return false;
}If we change those to (Note that this is consistent with every other implementation of |
Quoted from It means bean in context are not reused, it will create two instances, I don't like this behavior. |
Because they would not be beans. That's the point.
It will create one instance per persistence unit, which is completely fine. |
Take spring for example, I mean one instance for spring container, one instance for Hibernate, I need it be real singleton. |
You need what to be a singleton? |
The proposed |
This is what we were discussing yesterday, Gavin. Most people will just expect a CDI integration to involve actual CDI beans and to comply with scopes, and frankly I agree with them: it's just KISS. I know you disagree, at least in general, and I know there are technical reasons to not do that for e.g. ID generators and connection providers. That being said, putting aside other component types, I thought you agreed it was reasonable to do that for the tenant identifier resolver? EDIT: That being said, @quaff, regarding |
|
So as I was saying to Yoann on Zulip, I can see that I absolutely struggle to see how this could ever possibly be legit for any sort of connection provider. Connection providers have persistence-unit-specific configuration. And any contextual state which they might need can be injected into them. |
OK, I get the point, I changed |
I mean, just I don't see that.
I don't think you do expect that, so why would you expect it here? From where would the assumption that one can inject a
Right, correct, I think there's a reasonable argument that
Therefore, by making I'm much more concerned about |
Yeah, that looks fine now. |
Indeed, you could choose to view the And that explains why it's a special case. |
I do not expect to need to inject servlets, interceptors, or most pluggable Hibernate components into my application -- with the exception of tenant identifier resolvers and some others. I do however expect that if servlets, interceptors, or pluggable Hibernate components are involved in CDI, then:
These expectations may not match current implementations, or even the specs, but I think they are reasonable from an UX point of view, and they do comply with the principle of least surprise. |
...rc/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionProviderInitiator.java
Outdated
Show resolved
Hide resolved
…ectionProvider from BeanContainer if not explicit set
gavinking
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@quaff, thanks, but please see my further comments.
| final BeanContainer beanContainer = Helper.allowExtensionsInCdi( serviceRegistry ) ? serviceRegistry.requireService( ManagedBeanRegistry.class ).getBeanContainer() : null; | ||
| if (beanContainer != null) { | ||
| this.currentTenantIdentifierResolver = beanContainer.getBean( | ||
| CurrentTenantIdentifierResolver.class, | ||
| new BeanContainer.LifecycleOptions() { | ||
| @Override | ||
| public boolean canUseCachedReferences() { | ||
| return true; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean useJpaCompliantCreation() { | ||
| return false; | ||
| } | ||
| }, | ||
| new BeanInstanceProducer() { | ||
|
|
||
| @Override | ||
| public <B> B produceBeanInstance(Class<B> beanType) { | ||
| return null; | ||
| } | ||
|
|
||
| @Override | ||
| public <B> B produceBeanInstance(String name, Class<B> beanType) { | ||
| return null; | ||
| } | ||
|
|
||
| } | ||
| ).getBeanInstance(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please extract out a function for this, the constructor is already waaaaaay too long.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gavinking I suggest to introduce new util methods for org.hibernate.resource.beans.internal.Helper and refactor all BeanContainer access to use those methods in another PR, WDYT?
@SuppressWarnings( "unchecked" )
public static <T> T getBean(ServiceRegistry serviceRegistry, Class<?> beanType, boolean canUseCachedReferences, boolean useJpaCompliantCreation, T fallback) {
final BeanContainer beanContainer = allowExtensionsInCdi( serviceRegistry ) ? serviceRegistry.requireService( ManagedBeanRegistry.class ).getBeanContainer() : null;
if ( beanContainer == null ) {
return null;
}
return (T) beanContainer.getBean(
beanType,
new BeanContainer.LifecycleOptions() {
@Override
public boolean canUseCachedReferences() {
return canUseCachedReferences;
}
@Override
public boolean useJpaCompliantCreation() {
return useJpaCompliantCreation;
}
},
new BeanInstanceProducer() {
@Override
public <B> B produceBeanInstance(Class<B> beanType) {
return (B) fallback;
}
@Override
public <B> B produceBeanInstance(String name, Class<B> beanType) {
throw new UnsupportedOperationException("The method shouldn't be called");
}
}
).getBeanInstance();
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest to introduce new util methods for org.hibernate.resource.beans.internal.Helper and refactor all
BeanContaineraccess to use those methods
That sounds fine to me, but please do it now, so that it doesn't get forgotten. Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I created https://hibernate.atlassian.net/browse/HHH-18737 just now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, great, thanks 🙏
| return beanContainer.getBean( | ||
| ConnectionProvider.class, | ||
| new BeanContainer.LifecycleOptions() { | ||
| @Override | ||
| public boolean canUseCachedReferences() { | ||
| return true; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean useJpaCompliantCreation() { | ||
| return true; | ||
| } | ||
| }, | ||
| new BeanInstanceProducer() { | ||
|
|
||
| @Override | ||
| public <B> B produceBeanInstance(Class<B> beanType) { | ||
| return (B) noAppropriateConnectionProvider(); | ||
| } | ||
|
|
||
| @Override | ||
| public <B> B produceBeanInstance(String name, Class<B> beanType) { | ||
| return (B) noAppropriateConnectionProvider(); | ||
| } | ||
|
|
||
| } | ||
| ).getBeanInstance(); | ||
| } | ||
| else { | ||
| return noAppropriateConnectionProvider(); | ||
| } | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, please extract a function.
| final BeanContainer beanContainer = Helper.allowExtensionsInCdi( registry ) ? registry.requireService( ManagedBeanRegistry.class ).getBeanContainer() : null; | ||
| if (beanContainer != null) { | ||
| return beanContainer.getBean( | ||
| MultiTenantConnectionProvider.class, | ||
| new BeanContainer.LifecycleOptions() { | ||
| @Override | ||
| public boolean canUseCachedReferences() { | ||
| return true; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean useJpaCompliantCreation() { | ||
| return true; | ||
| } | ||
| }, | ||
| new BeanInstanceProducer() { | ||
|
|
||
| @Override | ||
| public <B> B produceBeanInstance(Class<B> beanType) { | ||
| return null; | ||
| } | ||
|
|
||
| @Override | ||
| public <B> B produceBeanInstance(String name, Class<B> beanType) { | ||
| return null; | ||
| } | ||
|
|
||
| } | ||
| ).getBeanInstance(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, extract a function here, please.
[Please describe here what your change is about]
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license
and can be relicensed under the terms of the LGPL v2.1 license in the future at the maintainers' discretion.
For more information on licensing, please check here.
https://hibernate.atlassian.net/browse/HHH-15422