SSM多模块项目设置多数据源

在现在开发的过程中应该大多数朋友都有遇到过切换数据源的需求。比如现在常用的数据库读写分离,或者就是有两个数据库的情况,这些都需要用到切换数据源。
使用Spring的AbstractRoutingDataSource类来进行拓展多数据源。
该类就相当于一个dataSource的路由,用于根据key值来进行切换对应的dataSource。
下面简单来看下AbstractRoutingDataSource类的几段关键源码:

 @Override
    public Connection getConnection() throws SQLException {
        return determineTargetDataSource().getConnection();
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return determineTargetDataSource().getConnection(username, password);
    }

    /**
     * Retrieve the current target DataSource. Determines the
     * {@link #determineCurrentLookupKey() current lookup key}, performs
     * a lookup in the {@link #setTargetDataSources targetDataSources} map,
     * falls back to the specified
     * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
     * @see #determineCurrentLookupKey()
     */
    protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        Object lookupKey = determineCurrentLookupKey();
        DataSource dataSource = this.resolvedDataSources.get(lookupKey);
        if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
            dataSource = this.resolvedDefaultDataSource;
        }
        if (dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
        }
        return dataSource;
    }

    /**
     * Determine the current lookup key. This will typically be
     * implemented to check a thread-bound transaction context.
     * <p>Allows for arbitrary keys. The returned key needs
     * to match the stored lookup key type, as resolved by the
     * {@link #resolveSpecifiedLookupKey} method.
     */
    protected abstract Object determineCurrentLookupKey();

可以看到其中获取链接的方法getConnection()调用的determineTargetDataSource则是关键方法。该方法用于返回我们使用的数据源。
其中呢又是determineCurrentLookupKey()方法来返回当前数据源的key值。
之后通过该key值在resolvedDataSources这个map中找到对应的value(该value就是数据源)。
resolvedDataSources这个map则是在:

 @Override
    public void afterPropertiesSet() {
        if (this.targetDataSources == null) {
            throw new IllegalArgumentException("Property 'targetDataSources' is required");
        }
        this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
        for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
            Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
            DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
            this.resolvedDataSources.put(lookupKey, dataSource);
        }
        if (this.defaultTargetDataSource != null) {
            this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
        }
    }

这个方法通过targetDataSources这个map来进行赋值的。targetDataSources则是我们在配置文件中进行赋值的,下面会讲到。

再来看看determineCurrentLookupKey()方法,从protected来修饰就可以看出是需要我们来进行重写的。

第一步:创建一个DynamicDataSource的类,继承AbstractRoutingDataSource并重写determineCurrentLookupKey方法,代码如下:

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        // 从自定义的位置获取数据源标识
        return DynamicDataSourceHolder.getDataSource();
    }

}

第二步:创建DynamicDataSourceHolder用于持有当前线程中使用的数据源标识,代码如下:

public class DynamicDataSourceHolder {
    /**
     * 注意:数据源标识保存在线程变量中,避免多线程操作数据源时互相干扰
     */
    private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<String>();

    public static String getDataSource() {
        return THREAD_DATA_SOURCE.get();
    }

    public static void setDataSource(String dataSource) {
        THREAD_DATA_SOURCE.set(dataSource);
    }

    public static void clearDataSource() {
        THREAD_DATA_SOURCE.remove();
    }
}

第三步:配置多个数据源和第一步里创建的DynamicDataSource的bean,简化的配置如下:applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx"

    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
        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-4.3.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">



    <mvc:annotation-driven />


    <context:property-placeholder location="classpath:jdbc.properties" /> 


    <bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="mysqldataSource">
         <property name="driverClass" value="${driverClass}"></property>
        <property name="jdbcUrl" value="${url}"></property>
        <property name="user" value="${user}"></property>
        <property name="password" value="${password}"></property> 
    </bean>

    <bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="pgdataSource">
         <property name="driverClass" value="${pgdriverClass}"></property>
        <property name="jdbcUrl" value="${pgurl}"></property>
        <property name="user" value="${pguser}"></property>
        <property name="password" value="${pgpassword}"></property> 

    </bean>

    <bean id="dataSource" class="com.taas.common.base.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <!-- 指定lookupKey和与之对应的数据源 -->
                <entry key="mysqldataSource" value-ref="mysqldataSource"></entry>
                <entry key="pgdataSource" value-ref="pgdataSource"></entry>
            </map>
        </property>
        <!-- 这里可以指定默认的数据源 -->
        <property name="defaultTargetDataSource" ref="mysqldataSource" />
    </bean>

    <bean id="dataSourceAspect" class="com.helloworld.common.base.DataSourceAspect" />
        <aop:config>
                <aop:aspect ref="dataSourceAspect">
                <!-- 拦截所有service方法 -->
                    <aop:pointcut id="dataSourcePointcut"
                                    expression="execution(* com.helloworld.*.service.*.*(..))" />
                    <aop:before pointcut-ref="dataSourcePointcut" method="intercept" />
                </aop:aspect>
        </aop:config>
    <!-- sqlSessionFactory -->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory">
        <property name="dataSource" ref="dataSource"></property>
        <property name="configLocation" value="classpath:mybatis.xml"></property>
        <property name="mapperLocations" value="classpath*:com/helloworld/*/mapping/*.xml"></property>
    </bean>

    <!-- mapper scan -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
        <property name="basePackage" value="com.helloworld.*.dao"></property>
    </bean>

    <bean       class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
        id="transactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager" />


</beans>

到这里已经可以使用多数据源了,在操作数据库之前只要DynamicDataSourceHolder.setDataSource(“dataSource2”)即可切换到数据源2并对数据库db2进行操作了。
示例代码如下:

@Service
 public class DataServiceImpl implements DataService {
    @Autowired
     private DataMapper dataMapper;

     @Override
     public List<Map<String, Object>> getList1() {
        // 没有指定,则默认使用数据源1
         return dataMapper.getList1();
     }

     @Override
     public List<Map<String, Object>> getList2() {
         // 指定切换到数据源2
         DynamicDataSourceHolder.setDataSource("dataSource2");
         return dataMapper.getList2();
     }

 }

但是每次切换数据源时都调用DynamicDataSourceHolder.setDataSource(“xxx”)就显得十分繁琐了,而且代码量大了很容易会遗漏,后期维护起来也比较麻烦。
所以,最好的方法还是直接通过注解的方式指定需要访问的数据源,比如在dao层使用@DataSource(“xxx”)就指定访问数据源xxx。
我们得定义一个名为DataSource的注解

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {

    String value();
}

然后,定义AOP切面以便拦截所有带有注解@DataSource的方法,取出注解的值作为数据源标识放到DynamicDataSourceHolder的线程变量中:


import java.lang.reflect.Method;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;


public class DataSourceAspect {
/**
      * 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源
      * 
      * @param point
      * @throws Exception
      */
     public void intercept(JoinPoint point) throws Exception {
         Class<?> target = point.getTarget().getClass();
         MethodSignature signature = (MethodSignature) point.getSignature();
         // 默认使用目标类型的注解,如果没有则使用其实现接口的注解
         for (Class<?> clazz : target.getInterfaces()) {
             resolveDataSource(clazz, signature.getMethod());
         }
         resolveDataSource(target, signature.getMethod());
     }

     /**
      * 提取目标对象方法注解和类型注解中的数据源标识
      * 
      * @param clazz
      * @param method
     */
     private void resolveDataSource(Class<?> clazz, Method method) {
         try {
             Class<?>[] types = method.getParameterTypes();
             // 默认使用类型注解
             if (clazz.isAnnotationPresent(DataSource.class)) {
                 DataSource source = clazz.getAnnotation(DataSource.class);
                 DynamicDataSourceHolder.setDataSource(source.value());
             }
             // 方法注解可以覆盖类型注解
             Method m = clazz.getMethod(method.getName(), types);
             if (m != null && m.isAnnotationPresent(DataSource.class)) {
                 DataSource source = m.getAnnotation(DataSource.class);
                 DynamicDataSourceHolder.setDataSource(source.value());
             }
         } catch (Exception e) {
             System.out.println(clazz + ":" + e.getMessage());
         }
     }

}

最后在spring配置文件中配置拦截规则就可以了,比如拦截service层或者dao层的所有方法: 上面已经贴了相关代码了,醒目一点,再贴个重要的吧

<bean id="dataSourceAspect" class="com.helloworld.common.base.DataSourceAspect" />
        <aop:config>
                <aop:aspect ref="dataSourceAspect">
                <!-- 拦截所有service方法 -->
                    <aop:pointcut id="dataSourcePointcut"
                                    expression="execution(* com.helloworld.*.service.*.*(..))" />
                    <aop:before pointcut-ref="dataSourcePointcut" method="intercept" />
                </aop:aspect>
        </aop:config>

现在就可以,直接在类或者方法上使用注解@DataSource来指定数据源,不需要每次都手动设置了。

import javax.annotation.Resource;

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



@Service
@DataSource("mysqldataSource")
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;


    public int deleteByPrimaryKey(Integer id) {
        // TODO Auto-generated method stub
        return userMapper.deleteByPrimaryKey(id);
    }

    public int insert(User record) {
        // TODO Auto-generated method stub
        return userMapper.insert(record);
    }

    public int insertSelective(User record) {
        // TODO Auto-generated method stub
        return userMapper.insertSelective(record);
    }

    public User selectByPrimaryKey(Integer id) {
        // TODO Auto-generated method stub
        return userMapper.selectByPrimaryKey(id);
    }

    public int updateByPrimaryKeySelective(User record) {
        // TODO Auto-generated method stub
        return userMapper.updateByPrimaryKeySelective(record);
    }

    public int updateByPrimaryKey(User record) {
        // TODO Auto-generated method stub
        return userMapper.updateByPrimaryKey(record);
    }

}

提示:注解@DataSource既可以加在方法上,也可以加在接口或者接口的实现类上,优先级别:方法>实现类>接口。也就是说如果接口、接口实现类以及方法上分别加了@DataSource注解来指定数据源,则优先以方法上指定的为准。

参考大神博客:http://blog.csdn.net/a15020059230/article/details/76677322

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个简单的SSM模块项目: 1. 首先,创建一个Maven项目,选择一个合适的名称(例如“ssm-multimodule-project”)和位置。在创建时,选择“Create a simple project(创建一个简单的项目)”,并勾选“Create a new module(创建新模块)”选项。 2. 在项目根目录下,创建一个名为“parent”的模块。在“parent”模块下的pom.xml文件,指定项目的groupId、artifactId和version,并添加以下内容: ```xml <packaging>pom</packaging> <modules> <module>web</module> <module>service</module> <module>dao</module> </modules> <dependencies> <!-- 添加Spring依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.8.RELEASE</version> </dependency> <!-- 添加MyBatis依赖 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.4</version> </dependency> </dependencies> ``` 在这里,我们创建了一个Maven父子模块项目,定义了三个子模块(web、service和dao),并添加了Spring和MyBatis的依赖。 3. 创建一个名为“web”的模块,用于实现Web服务。在“web”模块下的pom.xml文件,指定项目的groupId、artifactId和version,并添加以下内容: ```xml <packaging>war</packaging> <dependencies> <!-- 添加SpringMVC依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.8.RELEASE</version> </dependency> <!-- 添加JSP依赖 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies> ``` 在这里,我们创建了一个War包,添加了SpringMVC和JSP的依赖。 4. 创建一个名为“service”的模块,用于实现服务层。在“service”模块下的pom.xml文件,指定项目的groupId、artifactId和version,并添加以下内容: ```xml <packaging>jar</packaging> <dependencies> <!-- 添加Spring依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.8.RELEASE</version> </dependency> </dependencies> ``` 在这里,我们创建了一个Jar包,添加了Spring的依赖。 5. 创建一个名为“dao”的模块,用于实现数据访问层。在“dao”模块下的pom.xml文件,指定项目的groupId、artifactId和version,并添加以下内容: ```xml <packaging>jar</packaging> <dependencies> <!-- 添加MyBatis依赖 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.4</version> </dependency> <!-- 添加MySQL驱动依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.21</version> </dependency> </dependencies> ``` 在这里,我们创建了一个Jar包,添加了MyBatis和MySQL驱动的依赖。 6. 在“web”模块下的src/main/webapp目录下,创建一个名为“WEB-INF”的目录,并在其创建一个名为“web.xml”的文件。在“web.xml”文件,添加以下内容: ```xml <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <display-name>ssm-multimodule-project</display-name> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:application-context.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app> ``` 在这里,我们定义了SpringMVC的DispatcherServlet,并指定了SpringMVC配置文件的位置。 7. 在“web”模块下的src/main/resources目录下,创建一个名为“spring-mvc.xml”的文件,并添加以下内容: ```xml <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd"> <mvc:annotation-driven /> <mvc:resources location="classpath:/static/" mapping="/static/**" /> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> </bean> </beans> ``` 在这里,我们定义了SpringMVC的注解驱动、静态资源访问路径和视图解析器。 8. 在“web”模块下的src/main/resources目录下,创建一个名为“application-context.xml”的文件,并添加以下内容: ```xml <beans xmlns="http://www.springframework.org/schema/beans" 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-4.0.xsd"> <import resource="classpath*:application-context-*.xml" /> </beans> ``` 在这里,我们定义了应用上下文,引入了其他配置文件。 9. 在“service”模块下的src/main/resources目录下,创建一个名为“application-context-service.xml”的文件,并添加以下内容: ```xml <beans xmlns="http://www.springframework.org/schema/beans" 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-4.0.xsd"> <!-- 定义Service组件 --> <bean id="userService" class="com.example.service.UserService"> <property name="userDao" ref="userDao" /> </bean> </beans> ``` 在这里,我们定义了一个UserService组件,并注入了UserDao组件。 10. 在“dao”模块下的src/main/resources目录下,创建一个名为“application-context-dao.xml”的文件,并添加以下内容: ```xml <beans xmlns="http://www.springframework.org/schema/beans" 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-4.0.xsd"> <!-- 配置MyBatis --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/test" /> <property name="username" value="root" /> <property name="password" value="password" /> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.dao" /> </bean> </beans> ``` 在这里,我们定义了MyBatis的数据源、SqlSessionFactory和MapperScannerConfigurer。 11. 在“dao”模块下的src/main/java目录下,创建一个名为“com.example.dao”的包,并在其创建一个名为“UserDao”的接口和一个名为“UserDaoImpl”的实现类。在“UserDao”接口定义数据访问方法,如下所示: ```java public interface UserDao { User findUserById(long id); } ``` 在“UserDaoImpl”实现类,实现“UserDao”接口的方法,并使用MyBatis的注解来映射SQL语句,如下所示: ```java @Repository public class UserDaoImpl implements UserDao { @Autowired private SqlSession sqlSession; public User findUserById(long id) { return sqlSession.selectOne("com.example.UserMapper.findUserById", id); } } ``` 在这里,我们使用了@Repository注解,将UserDaoImpl组件声明为Spring的Repository组件,以便在其他组件进行依赖注入。 12. 在“service”模块下的src/main/java目录下,创建一个名为“com.example.service”的包,并在其创建一个名为“UserService”的类。在“UserService”类,实现业务逻辑,并注入“UserDao”组件,如下所示: ```java @Service public class UserService { @Autowired private UserDao userDao; public User findUserById(long id) { return userDao.findUserById(id); } } ``` 在这里,我们使用了@Service注解,将UserService组件声明为Spring的Service组件,以便在其他组件进行依赖注入。 13. 在“web”模块下的src/main/java目录下,创建一个名为“com.example.web”的包,并在其创建一个名为“UserController”的类。在“UserController”类,实现对用户数据的访问,并使用SpringMVC的注解来处理HTTP请求和响应,如下所示: ```java @Controller public class UserController { @Autowired private UserService userService; @RequestMapping(value = "/user/{id}", method = RequestMethod.GET) public ModelAndView getUser(@PathVariable long id) { ModelAndView mav = new ModelAndView("user"); User user = userService.findUserById(id); mav.addObject("user", user); return mav; } } ``` 在这里,我们使用了@Controller注解,将UserController组件声明为SpringMVC的Controller组件,以便处理HTTP请求和响应。 14. 在“web”模块下的src/main/webapp/WEB-INF/views目录下,创建一个名为“user.jsp”的文件,并添加以下内容: ```jsp <html> <head> <title>User</title> </head> <body> <h1>User Details</h1> <p>ID: ${user.id}</p> <p>Name: ${user.name}</p> <p>Email: ${user.email}</p> </body> </html> ``` 在这里,我们定义了用户详情页面,用于显示用户的详细信息。 15. 运行项目。在控制台输入以下命令: ``` mvn clean install tomcat7:run ``` 该命令将编译并打包项目,并启动Tomcat服务器。在浏览器访问http://localhost:8080/ssm-multimodule-project/user/1,即可查看ID为1的用户的详细信息。 至此,我们已经完成了一个简单的SSM模块项目。在实际项目,可能需要添加更多的模块和功能,但是本例提供了一个SSM模块项目的基本结构和实现方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值