Spring mvc mybaties druid 搭建多数据源

今天闲来无时,练习了一下Spring mvc +mybaties + druid 搭建多数据源。

下面使用了mysql 和 postgresql 为例,话不多说,直接看代码:


配置文件

jdbc.properties

mysql.jdbc.driverClassName=com.mysql.jdbc.Driver
mysql.jdbc.url=jdbc:mysql://localhost:3306/jazz?characterEncoding=utf-8
mysql.jdbc.username=root
mysql.jdbc.password=root

postgresql.jdbc.driverClassName=org.postgresql.Driver
postgresql.jdbc.url=jdbc:postgresql://localhost:5432/BJC20161122
postgresql.jdbc.username=myerp
postgresql.jdbc.password=godblessme

spring-mvc-applicatonContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
			http://www.springframework.org/schema/task  http://www.springframework.org/schema/task/spring-task-3.2.xsd"
      >
    <!-- 定义受环境影响易变的变量,例如数据库配置属性文件,应用程序属性文件等 -->
	<context:property-placeholder location="classpath:*.properties"/>
	
	<!-- component-scan自动搜索@Component , @Controller , @Service , @Repository等标注的类 -->
	<context:component-scan base-package="com.**.service,bean" />
	
    <!-- 配置DataSource数据源 -->
    <bean id="mysqlDataSourceSpied" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!-- 数据库连接 -->
		<property name="driverClassName" value="${mysql.jdbc.driverClassName}" />
		<property name="url" value="${mysql.jdbc.url}" />
		<property name="username" value="${mysql.jdbc.username}" />
		<property name="password" value="${mysql.jdbc.password}" />
		
		<property name="filters" value="stat"/>
		<!-- 连接池设置 -->
		<property name="initialSize" value="2" />
		<property name="maxActive" value="100" />
		<property name="maxWait" value="30000" />
		<property name="poolPreparedStatements" value="false" />
		<property name="defaultAutoCommit" value="false" />
		
		<property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <property name="minEvictableIdleTimeMillis" value="300000"/>

        <property name="validationQuery" value="SELECT 'x' from dual"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>
    </bean>
    
	<bean id="mysqlDataSource" class="net.sf.log4jdbc.sql.jdbcapi.DataSourceSpy">
		<constructor-arg ref="mysqlDataSourceSpied" />
	</bean>
	
	<!-- 配置DataSource数据源 -->
    <bean id="postgresqlDataSourceSpied" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!-- 数据库连接 -->
		<property name="driverClassName" value="${postgresql.jdbc.driverClassName}" />
		<property name="url" value="${postgresql.jdbc.url}" />
		<property name="username" value="${postgresql.jdbc.username}" />
		<property name="password" value="${postgresql.jdbc.password}" />
		
		<property name="filters" value="stat"/>
		<!-- 连接池设置 -->
		<property name="initialSize" value="2" />
		<property name="maxActive" value="100" />
		<property name="maxWait" value="30000" />
		<property name="poolPreparedStatements" value="false" />
		<property name="defaultAutoCommit" value="false" />
		
		<property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <property name="minEvictableIdleTimeMillis" value="300000"/>

        <property name="validationQuery" value="SELECT 1"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>
    </bean>
    
	<bean id="postgresqlDataSource" class="net.sf.log4jdbc.sql.jdbcapi.DataSourceSpy">
		<constructor-arg ref="postgresqlDataSourceSpied" />
	</bean>
	
	<bean id="dataSource" class="com.jazz.first.datasource.DynamicDataSource">
        <property name="defaultTargetDataSource" ref="mysqlDataSource" />
        <property name="targetDataSources">
            <map key-type="com.jazz.first.datasource.SourcesEnum">
                <entry key="mysql" value-ref="mysqlDataSource"/>
                <entry key="postgresql" value-ref="postgresqlDataSource"/>
                <!-- 这里还可以加多个dataSource -->
            </map>
        </property>
    </bean>   
	
    <!-- SqlSesstion Factory 定义 ↓ mybatis文件 -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<!-- 自动扫描entity目录,省略Configuration.xml里手工配置 -->
		<property name="mapperLocations" value="classpath*:/com/**/dao/mapper/*Mapper.xml"/>
		<property name="plugins">
			<array>
				<bean class="com.github.pagehelper.PageHelper">
					<property name="properties">
						<value>
							pageSizeZero=true
							rowBoundsWithCount=true
							reasonable=true
							offsetAsPageNum=true
							params=pageNum=offset;pageSize=limit;
							supportMethodsArguments=true
							returnPageInfo=check
						</value>
					</property>
				</bean>
			</array>
		</property>
	</bean>
	<!-- SqlSession Factory 定义 ↑ -->

	<!-- MapperScannerConfigurer定义,用于扫描Mapper DAO类 ↓ -->
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<property name="basePackage" value="com.**.dao.**"/>
		<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
	</bean>
    <!-- MapperScannerConfigurer定义,用于扫描Mapper DAO类 ↑ -->
	
	<!-- 数据库事务管理 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- ↓ ↓ 使用注解事务 ↓ ↓ -->
	<!-- <tx:annotation-driven transaction-manager="prjTxManager" proxy-target-class="true"/> -->
	<!-- ↑ ↑ 使用注解事务 ↑ ↑  -->
    
    <tx:advice id="txAdvice" transaction-manager="transactionManager">   	
        <tx:attributes>
            <tx:method name="create*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="save*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="add*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="delete*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="crud*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="schedule*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="do*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="*" />
        </tx:attributes>
    </tx:advice>
    
    <aop:aspectj-autoproxy proxy-target-class="true" />
    <aop:config>
        <aop:advisor id="managerTx" advice-ref="txAdvice" pointcut="execution(* *..service..*.*(..))" order="2"/>
    </aop:config>
    
    <bean id="dataSourceAspect" class="com.jazz.first.datasource.DataSourceAspect" />    
	<aop:config proxy-target-class="true">    
	    <aop:aspect id="dataSourceAspect" ref="dataSourceAspect" order="1">    
	        <aop:pointcut id="tx" expression="execution(* *..service..*.*(..)) "/>    
	        <aop:before pointcut-ref="tx" method="before" />                
	    </aop:aspect>    
	</aop:config>
   
	<!-- 公共dao -->
	<context:component-scan base-package="com.**.dao" />
</beans>

注意:由于我使用的注解式事务,和我们的AOP数据源切面有一个顺序的关系。数据源切换必须先执行,数据库事务才能获取到正确的数据源。所以要明确指定 注解式事务 order=2和 我们AOP数据源切面order=1。


java代码

AbstractRoutingDataSource 是spring提供的一个多数据源抽象类。spring会在使用事务的地方来调用此类的determineCurrentLookupKey()方法来获取数据源的key值。我们继承此抽象类并实现此方法:

package com.jazz.first.datasource;

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

public class DynamicDataSource extends AbstractRoutingDataSource {  
 
    @Override  
    protected Object determineCurrentLookupKey() {
    	
    	return HandleDataSource.getDataSource();  
    }  
 
}

DataSourceRouter  类中通过 HandleDataSource.getDataSource() 获取数据源的key值。此方法应该和线程绑定。

package com.jazz.first.datasource;

/**
 * 线程相关的数据源处理类
 * @author 
 */
public class HandleDataSource {
	
	// 数据源名称线程池
	private static final ThreadLocal<SourcesEnum> contextHolder = new ThreadLocal<SourcesEnum>();
	
	/**
	 * 获取数据源
	 * @return 数据源名称
	 */
	public static void setDataSource(SourcesEnum dbType) {  
           contextHolder.set(dbType);  
    }
	
	/**
	  * 获取数据源
	  * @return 数据源名称
	  */
    public static SourcesEnum getDataSource() {  
           return contextHolder.get();  
    }  
    
    /**
     * 清空数据源
     */
    public static void clearDbType() {  
           contextHolder.remove();  
    }
}

对于spring来说,注解即简单方便且可读性也高。所以,我们也通过注解在service的方法前指定所用的数据源。我们先定义自己的注解类,其中value为数据源的key值。

package com.jazz.first.datasource;

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

/**
 * 数据源注解类
 * @author 
 *
 */
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
    SourcesEnum value();
}

通过枚举来对应不同的数据源,这里一定得与配置文件中的key相同。

package com.jazz.first.datasource;

public enum SourcesEnum {
	mysql,postgresql
}

最后,我们可以通过AOP拦截所有service方法,在方法执行之前获取方法上的注解:即数据源的key值。

package com.jazz.first.datasource;

import java.lang.reflect.Method;
import java.text.MessageFormat;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

/**
 * 切换数据源(不同方法调用不同数据源)
 */
@Aspect
@Component
@Order(1) //请注意:这里order一定要小于tx:annotation-driven的order,即先执行DataSourceAspect切面,再执行事务切面,才能获取到最终的数据源
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DataSourceAspect {
    static Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);

    /**
     * 切入点 service包及子孙包下的所有类
     */
    @Pointcut("execution(* *..service..*.*(..))")
    public void aspect() {
    }

    /**
     * 配置前置通知,使用在方法aspect()上注册的切入点
     */
    @Before("aspect()")
    public void before(JoinPoint point) {
        Class<?> target = point.getTarget().getClass();
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod() ;
        DataSource dataSource = null ;
        //从类初始化
        dataSource = this.getDataSource(target, method) ;
        //从接口初始化
        if(dataSource == null){
            for (Class<?> clazz : target.getInterfaces()) {
                dataSource = getDataSource(clazz, method);
                if(dataSource != null){
                    break ;//从某个接口中一旦发现注解,不再循环
                }
            }
        }

        if(dataSource != null && !StringUtils.isEmpty(dataSource.value()) ){
            HandleDataSource.setDataSource(dataSource.value());
        }
    }

    @After("aspect()")
    public void after(JoinPoint point) {
        //使用完记得清空
        HandleDataSource.setDataSource(null);
    }


    /**
     * 获取方法或类的注解对象DataSource
     * @param target      类class
     * @param method    方法
     * @return DataSource
     */
    public DataSource getDataSource(Class<?> target, Method method){
        try {
            //1.优先方法注解
            Class<?>[] types = method.getParameterTypes();
            Method m = target.getMethod(method.getName(), types);
            if (m != null && m.isAnnotationPresent(DataSource.class)) {
                return m.getAnnotation(DataSource.class);
            }
            //2.其次类注解
            if (target.isAnnotationPresent(DataSource.class)) {
                return target.getAnnotation(DataSource.class);
            }

        } catch (Exception e) {
            e.printStackTrace();
            logger.error(MessageFormat.format("通过注解切换数据源时发生异常[class={0},method={1}]:"
                    , target.getName(), method.getName()),e)  ;
        }
        return null ;
    }
}

这样多数据源就搭建起来了,我们只需要在 每个service方法前使用 @DataSource("数据源key") 注解即可。



总结:此框架弊端显而易见,就是在一个service方法中只能访问同一个数据库,不能进行切换。当然好处事比较灵活,可以配置多个数据源,方式一致,service方法上加上注解后则切换到指定数据源,方法执行完后,则回答原始数据源。

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
集成Druid需要以下步骤: 1. 首先在pom.xml中添加DruidMySQL的依赖: ```xml <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.21</version> </dependency> ``` 2. 在web.xml中添加Druid的Servlet和Filter: ```xml <servlet> <servlet-name>DruidStatView</servlet-name> <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>DruidStatView</servlet-name> <url-pattern>/druid/*</url-pattern> </servlet-mapping> <filter> <filter-name>DruidWebStatFilter</filter-name> <filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class> </filter> <filter-mapping> <filter-name>DruidWebStatFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> ``` 3. 创建Druid的配置类,并添加数据源相关配置: ```java @Configuration public class DruidConfig { @Value("${spring.datasource.url}") private String url; @Value("${spring.datasource.username}") private String username; @Value("${spring.datasource.password}") private String password; @Value("${spring.datasource.driver-class-name}") private String driverClassName; @Bean public DataSource druidDataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setDriverClassName(driverClassName); return dataSource; } } ``` 4. 修改application.properties(或者application.yml)配置文件,添加数据源相关配置: ```properties spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver ``` 5. 在Spring MVC配置文件中添加数据源配置: ```java @Configuration @EnableWebMvc @ComponentScan(basePackages = {"com.example"}) public class MvcConfig extends WebMvcConfigurerAdapter { @Autowired private DataSource dataSource; @Bean public JdbcTemplate jdbcTemplate() { return new JdbcTemplate(dataSource); } } ``` 这样就完成了Spring MVC集成Druid的配置。可以通过访问`http://localhost:port/druid`来查看Druid的监控页面,并进行相关的数据源监控和管理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值