简体   繁体   中英

How to build and wire Spring database dependency .JAR project for use with multiple .WAR projects?

I am building a suite of web services that all access the same database but using different credentials. So to modularize the database code I am moving it out of each web app .WAR files and giving the database calls its own .JAR file. So that if WebApp1 and WebApp2 use a getAllColumnsFromTable1 MyBatis call I don't have to define the same mapper and domain object in both projects. That way I only have to change the MyBatis code in one place if Table1 ever changes, and less copy and paste mistakes.

WebApp1 applicationContext.xml:

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">

  <context:annotation-config />

  <context:component-scan base-package="com.example.webapp1.service" />
  <context:component-scan base-package="com.example.webapp1.controller" />

  <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:/example/ds/WebApp1DS" />
  </bean>

  <bean id="databaseConnector" class="com.example.databaseconnector.connector"
    <property name="dataSource" ref="dataSource" />
  </bean>
</beans>

So what I want to pass to the database connector .JAR file is just the data source that the .WAR is using.

WebApp1 pom.xml :

<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>WebApp1</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>

  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>3.2.3.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>3.2.3.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>com.example</groupId>
      <artifactId>databaseconnector</artifactId>
      <version>0.0.1-SNAPSHOT</version>
    </dependency>
  </dependencies>
</project>

Then have something like this in one of the services in the .WAR

WebApp1 Service.java:

package com.example.webapp1.service;

import com.example.databaseconnector.domain.TableSearchResult;
import com.example.databaseconnector.persistence.TableMapper;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class Service {
  @Autowired
  private TableMapper tableMapper;

  public List<TableSearchResult> getSearchResults(String param) {
    return tableMapper.getAllColumnsFromTable1(param);
  }
}

Then on the .JAR side I have

databaseconnector pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>databaseconnector</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>Database Connector</name>

  <dependencies>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.2.3</version>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.2.1</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>4.0.0.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>4.0.0.RELEASE</version>
    </dependency>
  </dependencies>
</project>

databaseconnector DatabaseConnection.java:

package com.example.databaseconnector.connector;

import javax.sql.DataSource;

import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

public class DatabaseConnection implements InitializingBean {
  private DataSource dataSource;
  private DataSourceTransactionManager dataSourceTransactionManager;
  private SqlSessionFactoryBean sqlSessionFactoryBean;
  private MapperScannerConfigurer mapperScannerConfigurer;

  public DataSource getDataSource() {
    return this.dataSource;
  }

  public void setDataSource(DataSource dataSource) {
    this.dataSource = dataSource;
  }

  @Override
  public void afterPropertiesSet() throws Exception {
    if (getDataSource() == null) {
      throw new IllegalArgumentException("Property 'dataSource' is required");
    } else {
      this.configureDataSourceTransactionManager();
      this.configureSqlSessionFactoryBean();
      this.configureMapperScannerConfigurer();
    }
  }

  private void configureDataSourceTransactionManager() {
    this.dataSourceTransactionManager = new DataSourceTransactionManager();

    this.dataSourceTransactionManager.setDataSource(dataSource);
  }

  private void configureSqlSessionFactoryBean() {
    this.sqlSessionFactoryBean = new SqlSessionFactoryBean();

    this.sqlSessionFactoryBean.setDataSource(dataSource);

    this.sqlSessionFactoryBean.setConfigLocation(
        new ClassPathResource("mybatis-config.xml"));

    this.sqlSessionFactoryBean.setTypeAliasesPackage(
        "com.example.sewp5databaseconnector.domain");
  }

  private void configureMapperScannerConfigurer() {
    this.mapperScannerConfigurer = new MapperScannerConfigurer();

    this.mapperScannerConfigurer.setBasePackage(
        "com.example.databaseconnector.persistence");

    this.mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactoryBean");
  }
}

When I run WebApp1 the program fails because it could not find the TableMapper bean to autowire the mapper in the service. What Spring setup am I missing to get this to all work together, or am I going about this the completely wrong way?

You seem to be missing your Spring configuration for the TableMapper bean. That class need to be a Spring bean in order for it to be injected into Service . Add the relevant bean configuration to your applicationContext.xml

Spring context hierarchy can help in common code scenario. If you have multiple webapps in a single EAR, EAR can have its own context, which is the parent of the individual webapp contexts. Also in each webapp, you can have one root context and individual children context as well. You can define this hierarchy in web.xml. Parent context can be specified through context parameters: locatorFactorySelector and parentContextKey. Root context through context parameter contextConfigLocation(outside servlet). Child context can be specified in the init param - context parameter of each of the servlet.

Have one jar in EAR holding all your common service and DAO layer code and define them in beanRefContext.xml(which is basically another application context xml). make this jar available in classpath.

In web.xml of each app where you want to refer parent context code:

    <!--  root application context -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:rootContextBeans.xml</param-value>
    </context-param>
    <!--  shared service layer - parent application context -->
    <context-param>
        <param-name>locatorFactorySelector</param-name>
        <param-value>classpath:beanRefContext.xml</param-value>
    </context-param>
    <context-param>
        <param-name>parentContextKey</param-name>
        <param-value>servicelayer-context</param-value>
    </context-param>
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>     
    <servlet>
        <servlet-name>dispatcherServletApp1</servlet-name> 
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 
        <init-param> 
            <param-name>contextConfigLocation</param-name> 
            <param-value>classpath*:webApp1.xml</param-value> 
        </init-param> 
    </servlet> 

where beanRefContext.xml will be like:

    <beans>
      <bean id="servicelayer-context" class="org.springframework.context.support.ClassPathXmlApplicationContext">
        <constructor-arg>
          <list>
            <value>data-layer-context.xml</value>
          </list>
        </constructor-arg>
      </bean>
    </beans>

This way you will achieve what you want.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM