如何使用嵌入式 Tomcat 使用 JNDI 数据库连接与 Spring Boot 和 Spring Data? [英] Howto use JNDI database connection with Spring Boot and Spring Data using embedded Tomcat?

查看:38
本文介绍了如何使用嵌入式 Tomcat 使用 JNDI 数据库连接与 Spring Boot 和 Spring Data?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我尝试使用嵌入式 Tomcat 服务器将 JNDI 数据源与 Spring Boot 和 Spring Data JPA 结合使用时,在使用 SpringApplication.run 运行应用程序时收到以下错误消息:

When I try to use a JNDI datasource with Spring Boot and Spring Data JPA using an embedded Tomcat server, I get the following error message when running the application with SpringApplication.run:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.class]: Instantiation of bean failed; 
nested exception is org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration.entityManagerFactory(org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryBuilder)] threw exception;
nested exception is org.springframework.jndi.JndiLookupFailureException: JndiObjectTargetSource failed to obtain new target object; 
nested exception is javax.naming.NameNotFoundException: Name [comp/env/jdbc/myDataSource] is not bound in this Context. Unable to find [comp].

我使用了如何在带有嵌入式 Tomcat 容器的 Spring Boot 中创建 JNDI 上下文

唯一的区别是对 org.springframework.boot:spring-boot-starter-data-jpa 的额外 Maven 依赖

The only difference is the additional Maven dependency to org.springframework.boot:spring-boot-starter-data-jpa

这是一个示例项目:https://github.com/derkoe/spring-boot-sample-tomcat-jndi(这是解决方案中示例的修改版本).只需检查、构建和运行 SampleTomcatJndiApplication.

Here is a sample project: https://github.com/derkoe/spring-boot-sample-tomcat-jndi (this is a modified version of the sample in the solution). Just check out, build and run SampleTomcatJndiApplication.

用于查找数据库连接的 JNDI 上下文似乎还不是来自 webapp 的上下文.这个好像是Spring上下文和Tomcat服务器初始化的顺序问题.

It seems that the JNDI context used in looking up the database connection is not yet the one from the webapp. This seems to be an ordering problem in the initialization of the Spring context and the Tomcat server.

任何想法如何解决?

推荐答案

Tomcat 使用线程的上下文类加载器来确定要执行查找的 JNDI 上下文.如果线程上下文类加载器不是 Web 应用类加载器,则 JNDI 上下文为空,因此查找失败.

Tomcat uses the thread's context class loader to determine the JNDI context to perform the lookup against. If the thread context class loader isn't the web app classloader then the JNDI context is empty, hence the lookup failure.

问题在于启动期间执行的 DataSource 的 JNDI 查找是在主线程上执行的,并且主线程的 TCCL 不是 Tomcat 的 Web 应用程序类加载器.您可以通过更新 TomcatEmbeddedServletContainerFactory bean 来设置线程上下文类加载器来解决此问题.我还没有说服自己这不是一个可怕的黑客,但它有效......

The problem is that the JNDI lookup of the DataSource that's performed during startup is being performed on the main thread and the main thread's TCCL isn't Tomcat's web app classloader. You can work around this by updating your TomcatEmbeddedServletContainerFactory bean to set the thread context class loader. I've yet to convince myself that this isn't a horrible hack, but it works…

这是更新后的 bean:

Here's the updated bean:

@Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
    return new TomcatEmbeddedServletContainerFactory() {

        @Override
        protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
                Tomcat tomcat) {
            tomcat.enableNaming();
            TomcatEmbeddedServletContainer container = 
                    super.getTomcatEmbeddedServletContainer(tomcat);
            for (Container child: container.getTomcat().getHost().findChildren()) {
                if (child instanceof Context) {
                    ClassLoader contextClassLoader = 
                            ((Context)child).getLoader().getClassLoader();
                    Thread.currentThread().setContextClassLoader(contextClassLoader);
                    break;
                }
            }
            return container;
        }

        @Override
        protected void postProcessContext(Context context) {
            ContextResource resource = new ContextResource();
            resource.setName("jdbc/myDataSource");
            resource.setType(DataSource.class.getName());
            resource.setProperty("driverClassName", "your.db.Driver");
            resource.setProperty("url", "jdbc:yourDb");

            context.getNamingResources().addResource(resource);
        }
    };
}

getEmbeddedServletContainer 提取上下文的类加载器并将其设置为当前线程的上下文类加载器.这发生在` 调用超级方法之后.这种排序很重要,因为对 super 方法的调用会创建并启动容器,并且作为该创建的一部分,会创建上下文的类加载器.

getEmbeddedServletContainer extracts the context's classloader and set it as the current thread's context class loader. This happens` after the call to the super method. This ordering is important as the call to the super method creates and starts the container and, as a part of that creation, creates the context's class loader.

这篇关于如何使用嵌入式 Tomcat 使用 JNDI 数据库连接与 Spring Boot 和 Spring Data?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆