aop的应用--使用Spring的AbstractRoutingDataSource+aop实现mybatis多数据源的配置

       在设计pojo生成工具的时候须要解决的一个问题就是因为数据库的多个致使这样的工程须要链接多个数据源,传统的都是一个数据源在spring启动的时候让数据源进行实例化,并进行初始化,在前面的一篇中大概的介绍了一下个人实现思路,当时是采用原生的mybatis经过请求在访问的时候,根据数据库的不一样,而去链接不一样的数据库,这样会存在一个问题,大量的请求会致使资源的浪费,严重影响性能,因而我考虑了采用了缓存的方式,将数据放到本地缓存里,可是这种方式也不是最优的,因而上网查了一下想关资料,对于多数源的处理,有好几种方式,我采用了aop+自定义注解的方式实现。html

       spring为咱们提供了AbstractRoutingDataSource, 该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上。实现思想就是在本身的内部维护多个数据源,当咱们经过自定义的注解指定时,这个路由中介就会自动的切换数据源,java

配置以下:git

<?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:context="http://www.springframework.org/schema/context"
	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">
	<!-- 配置整合mybatis过程 -->
	<!-- 1.配置数据库相关参数properties的属性:${url} -->
	<context:property-placeholder location="classpath:properties/*.properties" />


	<!-- 2.数据库链接池 -->
	<bean id="dataSource1" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<!-- 配置链接池属性 -->
		<property name="driverClass" value="${jdbc.driver}" />
		<property name="jdbcUrl" value="${jdbc.url}" />
		<property name="user" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />

		<!-- c3p0链接池的私有属性 -->
		<property name="maxPoolSize" value="30" />
		<property name="minPoolSize" value="10" />
		<!-- 关闭链接后不自动commit -->
		<property name="autoCommitOnClose" value="false" />
		<!-- 获取链接超时时间 -->
		<property name="checkoutTimeout" value="10000" />
		<!-- 当获取链接失败重试次数 -->
		<property name="acquireRetryAttempts" value="2" />
	</bean>

	<bean id="dataSource2" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<!-- 配置链接池属性 -->
		<property name="driverClass" value="${jdbc.driver}" />
		<property name="jdbcUrl" value="${jdbc.url2}" />
		<property name="user" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />

		<!-- c3p0链接池的私有属性 -->
		<property name="maxPoolSize" value="30" />
		<property name="minPoolSize" value="10" />
		<!-- 关闭链接后不自动commit -->
		<property name="autoCommitOnClose" value="false" />
		<!-- 获取链接超时时间 -->
		<property name="checkoutTimeout" value="10000" />
		<!-- 当获取链接失败重试次数 -->
		<property name="acquireRetryAttempts" value="2" />
	</bean>


	<bean id="dataSource" class="com.soecode.lyf.data.DataSourceRouter" lazy-init="true">
		<description>多数据源路由</description>
		<property name="targetDataSources">
			<map key-type="java.lang.String" value-type="javax.sql.DataSource">
				<!-- write -->
				<entry key="dataSource1" value-ref="dataSource1" />
				<entry key="dataSource2" value-ref="dataSource2" />
			</map>
		</property>
		<!-- 默认数据源,若是未指定数据源 或者指定的数据源不存在的话 默认使用这个数据源 -->
		<property name="defaultTargetDataSource" ref="dataSource1" />

	</bean>

	<!-- 3.配置SqlSessionFactory对象 -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<!-- 注入数据库链接池 -->
		<property name="dataSource" ref="dataSource" />
		<!-- 配置MyBaties全局配置文件:mybatis-config.xml -->
		<property name="configLocation" value="classpath:mybatis-config.xml" />
		<!-- 扫描entity包 使用别名 -->
		<property name="typeAliasesPackage" value="com.soecode.lyf.entity" />
		<!-- 扫描sql配置文件:mapper须要的xml文件 -->
		<property name="mapperLocations" value="classpath:mapper/*.xml" />
	</bean>



	<!-- 4.配置扫描Dao接口包,动态实现Dao接口,注入到spring容器中 -->
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<!-- 注入sqlSessionFactory -->
		<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
		<!-- 给出须要扫描Dao接口包 -->
		<property name="basePackage" value="com.soecode.lyf.dao" />
	</bean>
</beans>

配置的思想就是加个中介,在中介配置多数据源,由中介选择。github

实现spring的多路配置,由spring管理:spring

package com.soecode.lyf.data;

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

/**
 * @Description
 * @Author DJZ-WWS
 * 实现spring多路配置,由spring管理
 * @Date 2019/6/2 16:14
 */
public class DataSourceRouter   extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return HandleDataSource.getDataSource();
    }
}

 

使用ThreadLocal保证数据源的线程安全:sql

package com.soecode.lyf.data;

/**
 * @Description
 * @Author DJZ-WWS
 * @Date 2019/6/2 16:16
 */
public class HandleDataSource {
    // 数据源名称线程池
    private static final ThreadLocal<String> holder = new ThreadLocal<>();

    /**
     * 设置数据源
     * @param datasource 数据源名称
     */
    public static void setDataSource(String datasource) {
        holder.set(datasource);
    }
    /**
     * 获取数据源
     * @return 数据源名称
     */
    public static String getDataSource() {
        return holder.get();
    }
    /**
     * 清空数据源
     */
    public static void clearDataSource() {
        holder.remove();
    }
}

aop的切面类数据库

package com.soecode.lyf.data;

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;

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


/**
 * 切换数据源(不一样方法调用不一样数据源)
 */
@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(* com.soecode.lyf.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;
    }
}

自定义注解:缓存

package com.soecode.lyf.data;

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

/**
 * Create by wws on 2019/6/2
 */
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
    String value();
}

使用方式  经过自定义注解的方式实现数据源的配置。安全

 

 

有个问题须要注意一下的:mybatis

在这个实践过程当中碰到了一些问题,作一次记录

问题1:

在使用aop的时候没用生效

解决方式:

切入点表达式里面的execution方法是否正确:service..*是service包下全部的子孙包service..*.*(..)是service包下全部的子孙包下内的全部方法。

@Pointcut("execution(* com.soecode.lyf.service..*.*(..))")
    public void aspect() {
    }

是否引入了aop注解开启

<!-- 开启对 @Aspect 的支持-->
<aop:aspectj-autoproxy/>

问题2:

java.lang.IllegalArgumentException: No converter found for return value of type: 

在springmvc数据渲染的时候由于某种缘由致使数据渲染失败,返回的实体bean没有加get/set方法,准确的说应该是get方法

个人是因为本身疏忽,没有加get方法致使。

参考博客:https://www.jianshu.com/p/fddcc1a6b2d8

demo源码放在github上 https://github.com/wws11/ssm-.git