Skip to content

Class Persistence.createEntityManagerFactory() used in Verticle produced new Vertx Instance #995

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
codingxu97 opened this issue Oct 13, 2021 · 22 comments

Comments

@codingxu97
Copy link
Contributor

codingxu97 commented Oct 13, 2021

Here is my code:
Code
Console printed the following message:
Warn

@DavideD
Copy link
Member

DavideD commented Oct 13, 2021

I'm working on the documentation of this part, but basically you need to register the already existing Vert.x instance when you create the SessionFactory.

I've explained how to do it as an answer to this question.

Let me know if that helps. I will add that section to the documentation before the final release

@codingxu97
Copy link
Contributor Author

Thanks!
But I prefer using JPA Persistence instead of hibernate Configuration class,for it's simple and general.

@DavideD
Copy link
Member

DavideD commented Oct 13, 2021

The service loader mechanism described in the answer should work with the JPA approach as well.

@codingxu97
Copy link
Contributor Author

codingxu97 commented Oct 14, 2021

I tried to do this,but it not works.
Here is my code:
ServiceLoader config
Config location
Verticle code

Then,run the code:
Result

In addition,service loader seems to be used with classes which have a zero-argument constructor.
ServiceLoader docs
So,Class MyProvider in my code used default constructor.

ProvidedVertxInstance seems to be the best choice to use in service loader,but it has no zero-argument constructor.
ProvidedVertxInstance

@DavideD
Copy link
Member

DavideD commented Oct 14, 2021

Please, it would be better if you could attach the code as preformatted text and not using images.

You don't show what the class MyProvider looks like so I cannot tell you why it doesn't work.
Also, you don't need to access the VertxInstance service.

These are the two approaches:

  1. Create the factory programmatically:
    import org.hibernate.cfg.Configuration;
    
    public Stage.SessionFactory createFactory() {
        Configuration configuration = new Configuration();
        // ... set the values for your configuration 
    
       StandardServiceRegistryBuilder builder = new ReactiveServiceRegistryBuilder()
                                // Add the service that returns the current Vert.x instance
    			.addService( VertxInstance.class, (VertxInstance) () -> vertx )
    			.applySettings( configuration.getProperties() );
    
       StandardServiceRegistry registry = builder.build();
       return configuration.buildSessionFactory( registry )
           .unwrap( Stage.SessionFactory.class );
    }
    

UPDATED: I don't think the example I wrote initially for case 2 will work. I will test it and update the answer if I can find a better solution

  1. Implementing the interface VertxInstace.
    I'm not sure exactly how to make this work in this case. The problem is that you need to define a class that can access the
    Vert.x instance in MainClass. I will think a bit more about it and let you know if I can find a solution

I guess in this case you should go with the first solution.

@DavideD
Copy link
Member

DavideD commented Oct 14, 2021

Actually, I'm not sure the second approach will work. I will create a test and get back to you

@codingxu97
Copy link
Contributor Author

Sorry,I will no longer use code images.

As you told,the second approach didn't work.

I used MainVerticle which implements VertxInstace instead of MyProvider.

class MainVerticle : CoroutineVerticle(), VertxInstance {  }

But,it still create a new Vertx instance When I run following code:

override suspend fun start() {
    val sessionFactoryAwait = vertx.executeBlocking<Stage.SessionFactory> {
        // Get SessionFactory
        val sf: SessionFactory =
            Persistence
                .createEntityManagerFactory("mysql-demo")
                .unwrap(SessionFactory::class.java)
        // Get VertxInstance via docs
        val vertxViaDoc: VertxInstance =
            sf.unwrap(SessionFactoryImplementor::class.java)
                .serviceRegistry
                .getService(VertxInstance::class.java)
        // Get VertxInstance via Service Loader
        val loaderViaSpi: ServiceLoader<VertxInstance> = ServiceLoader.load(VertxInstance::class.java)
        val vertxViaSpi: VertxInstance = loaderViaSpi.single()
    
        println(vertxViaDoc.javaClass.simpleName)
        println(vertxViaSpi.javaClass.simpleName)
    
        val stageSf = sf.unwrap(Stage.SessionFactory::class.java)
        it.complete(stageSf)
    }
    
    val sessionFactory: Stage.SessionFactory = sessionFactoryAwait.await()
}

Run result:

18:25:50.322 [vert.x-worker-thread-0] WARN io.vertx.core.impl.VertxImpl - You're already on a Vert.x context, are you sure you want to create a new Vertx instance?
DefaultVertxInstance
MainVerticle

Here is my MainClass code:

suspend fun main() {
    val vertx = Vertx.vertx()
    vertx
        .deployVerticle("com.fospot.trader.hibernatetest.MainVerticle")
        .await()
}

@DavideD
Copy link
Member

DavideD commented Oct 14, 2021

I suppose you could try something like this:

suspend fun main() {
    val vertx = Vertx.vertx()
   MyProvider.vertx= vertx;
    vertx
        .deployVerticle("com.fospot.trader.hibernatetest.MainVerticle")
        .await()
}

public class MyProvider implements VertxInstance {
    public static Vertx vertx = null;

   @Override
   public void getVertx() {
       return vertx;
   }
}

But using the Hibernate configuration instead of the Persistence one looks better IMHO.

By the way, you can change the code to something more reactive by using this approach.

@codingxu97
Copy link
Contributor Author

Thanks a lot!

On your suggestion,it still create new one.Maybe I should give up using Persistence.

But,is there any possibility that Hibernate reactive could support to use Persistence in Verticle in the future?It seems that persistence.xml was more general than hibernate.cfg.xml IMHO.

@DavideD
Copy link
Member

DavideD commented Oct 14, 2021

On your suggestion,it still create new one.Maybe I should give up using Persistence.

That's weird, are you sure the service is defined correctly in your app? Does MyProvider get created and called?
If you could provide a test project I will check.

But,is there any possibility that Hibernate reactive could support to use Persistence in Verticle in the future?It seems that persistence.xml was more general than hibernate.cfg.xml IMHO.

The persistence.xml and the use of the class Persistence come from JPA JSR. This specification is not designed for reactive code and it doesn't define how to interact with Vert.x.

I think what we already have allows this already. How would you pass an instance of Vert.x in the persistence.xml?

@codingxu97
Copy link
Contributor Author

codingxu97 commented Oct 15, 2021

Here is my demo:
https://github.com/Mr-CodingXu/hibernate-demo.git

By the way, first approach using hibernate.cfg.xml works, Thanks!

@DavideD
Copy link
Member

DavideD commented Oct 15, 2021

I can see that the ServiceLoader is not loading MyProvider.

@codingxu97
Copy link
Contributor Author

Here is run result:

20:26:08.654 [vert.x-worker-thread-0] WARN io.vertx.core.impl.VertxImpl - You're already on a Vert.x context, are you sure you want to create a new Vertx instance?
DefaultVertxInstance
MyProvider

So, MyProvider will be printed to the console when I invoke ServiceLoader.load(VertxInstance::class.java).single().

@DavideD
Copy link
Member

DavideD commented Oct 15, 2021

Mmmh... I think you have found a bug

@codingxu97
Copy link
Contributor Author

codingxu97 commented Oct 15, 2021

Oh, I see!

In fact, I'd read the source code roughly yesterday, it seems that VertxInstanceInitiator instantiates VertxInstance using DefaultVertxInstance class, and I couldn't find out where service loader changes it.

// Definitely exclusive to Hibernate Reactive, as it marks the registry as Reactive:
serviceInitiators.add( ReactiveMarkerServiceInitiator.INSTANCE );

// Exclusive to Hibernate Reactive:
serviceInitiators.add( VertxInstanceInitiator.INSTANCE );
serviceInitiators.add( VertxContextInitiator.INSTANCE );

// Exclusive to Hibernate Reactive:
serviceInitiators.add( SqlClientPoolConfigurationInitiator.INSTANCE );
serviceInitiators.add( ReactiveConnectionPoolInitiator.INSTANCE );

In addtion, here is the method start() in DefaultVertxInstance class:

@Override
public void start() {
    vertx = Vertx.vertx();
}

Could Vertx.currentContext().owner() be used instead of Vertx.vertx() like following code?

@Override
public void start() {
    Context currentContext = Vertx.currentContext();
    vertx = currentContext != null ? currentContext.owner() : Vertx.vertx();
}

@DavideD
Copy link
Member

DavideD commented Oct 15, 2021

Could Vertx.currentContext().owner() be used instead of Vertx.vertx() like following code?

You know, I think that was the initial plan. @gavinking?
We could create it if there is no context and stop the service only in that case.

Anyway, I understand now why the service loader is not working.
I was loading the wrong service, I will publish the changes that work

@DavideD
Copy link
Member

DavideD commented Oct 15, 2021

@Mr-CodingXu Would you like to send a PR about changing DefaultVertxInstance? Just make sure to stop vert.x only if we are the one that have created it

@DavideD
Copy link
Member

DavideD commented Oct 15, 2021

Anyway, to register a custom VertxInstance using the java ServiceLoader the correct approach is to use a ServiceContributor:

package com.coding.demo.hibernate.provider

import org.hibernate.boot.registry.StandardServiceRegistryBuilder
import org.hibernate.reactive.vertx.VertxInstance
import org.hibernate.service.spi.ServiceContributor

class MyProviderContributor : ServiceContributor {
    override fun contribute(nullable: StandardServiceRegistryBuilder?) {
        var serviceRegistryBuilder = nullable!!
        serviceRegistryBuilder.addService(VertxInstance::class.java, MyProvider())
    }
}

and then regsiter it using the file:

/META-INF/services/org.hibernate.service.spi.ServiceContributor

I've forked the project you've provided and updated it on my repository: https://github.com/DavideD/hibernate-demo
Thanks a lot!

@codingxu97
Copy link
Contributor Author

@Mr-CodingXu Would you like to send a PR about changing DefaultVertxInstance? Just make sure to stop vert.x only if we are the one that have created it

Oh, yes! I'll think it over and try to do it in the shortest time.

@codingxu97
Copy link
Contributor Author

I've forked the project you've provided and updated it on my repository: https://github.com/DavideD/hibernate-demo Thanks a lot!

Your code refactoring offers me another train of thought for better code, thanks!

@DavideD
Copy link
Member

DavideD commented Oct 15, 2021

Thanks :-)
I'm not familiar with Kotlin so I'm sure there is space for improvement.

I'm closing this issue. It seems we figure out what we need to improve.
I've created #1001 to keep track of the changes to DefaultVertxInstance.

Thanks a lot again!

@DavideD DavideD closed this as completed Oct 15, 2021
@DavideD
Copy link
Member

DavideD commented Oct 15, 2021

I've clened up the demo a bit more: DavideD/hibernate-demo@c7c96aa

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants