在设计pojo生成工具的时候需要解决的一个问题就是由于数据库的多个导致这样的工程需要连接多个数据源,传统的都是一个数据源在spring启动的时候让数据源进行实例化,并进行初始化,在前面的一篇中大概的介绍了一下我的实现思路,当时是采用原生的mybatis通过请求在访问的时候,根据数据库的不同,而去连接不同的数据库,这样会存在一个问题,大量的请求会导致资源的浪费,严重影响性能,于是我考虑了采用了缓存的方式,将数据放到本地缓存里,但是这种方式也不是最优的,于是上网查了一下想关资料,对于多数源的处理,有好几种方式,我采用了aop+自定义注解的方式实现。
spring为我们提供了AbstractRoutingDataSource, 该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上。实现思想就是在自己的内部维护多个数据源,当我们通过自定义的注解指定时,这个路由中介就会自动的切换数据源,
配置如下:
<?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>
配置的思想就是加个中介,在中介配置多数据源,由中介选择。
实现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保证数据源的线程安全:
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();
}
使用方式 通过自定义注解的方式实现数据源的配置。
有个问题需要注意一下的:
在这个实践过程中碰到了一些问题,做一次记录
问题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