当我尝试通过嵌入式Tomcat服务器将JNDI数据源与Spring Boot和Spring Data JPA结合使用时,通过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附加了Maven依赖:spring-boot-starter-data-jpa
这是一个示例项目:https : //github.com/derkoe/spring-boot-sample-tomcat- jndi(这是解决方案中示例的修改版本)。只需签出,构建并运行SampleTomcatJndiApplication。
看起来,用于查找数据库连接的JNDI上下文还不是来自Webapp的。在Spring上下文和Tomcat服务器的初始化中,这似乎是一个排序问题。
任何想法如何解决?
Tomcat使用线程的上下文类加载器确定要执行查找的JNDI上下文。如果线程上下文类加载器不是Web应用程序类加载器,则JNDI上下文为空,因此查找失败。
问题在于,DataSource启动期间执行的JNDI查找是在主线程上执行的,而主线程的TCCL不是Tomcat的Web应用程序类加载器。您可以通过更新TomcatEmbeddedServletContainerFactorybean来设置线程上下文类加载器来解决此问题。我还没有说服自己,这不是一个可怕的骇客,但确实有效……
DataSource
TomcatEmbeddedServletContainerFactory
这是更新的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方法之后发生的。这种顺序非常重要,因为对super方法的调用会创建并启动容器,并在创建过程中创建上下文的类加载器。
getEmbeddedServletContainer