多数据库配置需求有两种,一种是因为项目太大,访问量太高,不得不分布多个数据库减轻访问压力,比较多的应用就是读写分离;另一种就是原本不同的两个数据库业务现在要整合到一起,甚至连数据库都不一样,一个mysql,一个sqlserver,小编目前的项目就是属于后者。
要实现读写分离,首先得保证主从复制,即写库的数据能实时复制到读库里,这样才能保证数据无差别,这不是今天要学习的内容,小编项目目前没用到~~本篇讲的是多数据库配置。整合多种数据库的方式有两种:分包和AOP,本文只记录AOP方式。
参考这篇文章:https://www.cnblogs.com/weixupeng/p/9720472.html (排版不太友好)
1、由于使用Maven,首先pom.xml引入要链接数据库的驱动jar包依赖,mysql可以直接引入,但sqlserver和orcale要先自己下载到本地,然后手动引入后才能添加依赖,添加方式如这篇sqlserver示例的文章:https://www.cnblogs.com/dawnheaven/p/5738477.html ,也可以从别的渠道下载最新版本,但记得mvn时要写正确的版本号。
mvn install:install-file -Dfile=sqljdbc4.jar -Dpackaging=jar -DgroupId=com.microsoft.sqlserver -DartifactId=sqljdbc4 -Dversion=4.0
这里记得改成自己的版本 -Dfile="jar包的绝对路径+完整文件名称版本"
此方式同样适用于Linux环境。
2、配置properties文件,这个文件每个人使用的名称可能不同,有的人新建个db.properties,有的是config.properties,这个无关紧要,在spring.xml文件中配置对应的自己文件就可以了。
3、配置spring.xml,这个文件也有很多不同的名字,有的文章用application-content.xml,有的文件叫springmvc.xml,我的叫spring-context.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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
有的说最后一行是 http://www.springframework.org/schema/aop/spring-aop-3.0.xsd 有的说是 http://www.springframework.org/schema/aop/spring-aop.xsd
需要在pom.xml中添加依赖:
<dependency> <groupId>org.aspectjgroupId> <artifactId>aspectjrtartifactId> <version>1.8.0version> dependency> <dependency> <groupId>org.aspectjgroupId> <artifactId>aspectjweaverartifactId> <version>1.8.0version> dependency>
其他的配置按别的文章都差不多,唯有一点需要注意!
<bean id="dynamicDataSource" class="com.dataSourcer.ThreadLocalRountingDataSource"> <property name="defaultTargetDataSource" ref="dataSource_daka"/> <property name="targetDataSources"> <map key-type="com.dataSourcer.DataSources"> 这个地方如果java代码定义的枚举,则需要使用枚举类,否则可以使用java.lang.String <entry key="daka" value-ref="dataSource_daka">entry> 这里要与自定义枚举类的key值一致 <entry key="kaoqin" value-ref="dataSource_kaoqin">entry> map> property> bean>
4、定义自己的数据库枚举类。
5、定义ThreadLocalRountingDataSource类继承自AbstractRoutingDataSource,这个基本都一样没什么特殊的。
6、自定义注解类,这个很重要但没什么要说的。
7、定义数据库管理类DataSourceTypeManager(有的用 DataSourceContextHolder 命名,个人感觉还是Manager好理解些)。此类中的ThreadLocal实现线程安全还是要加的,而且特别推荐以下写法:
// ThreadLocal类是实现线程安全的关键,因为数据操作大部分都是并发执行,所以必须要考虑线程安全 private static final ThreadLocal<DataSources> dataSourceTypes = new ThreadLocal<DataSources>() { @Override protected DataSources initialValue() { return DataSources.daka; } };
8、最后重头戏DynamicDataSourceAspect,其中可以定义切点,当然也可以定义在spring.xml中。
最后以下为我的项目的代码汇总:
jdbc.url = jdbc:mysql://IP:3306/数据库名称?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false jdbc.username = 账号 jdbc.password= 密码 sql.url = jdbc:sqlserver://ip:1433;databaseName=数据库名称 sql.username = 账号 sql.password= 密码
<dependency> <groupId>org.aspectjgroupId> <artifactId>aspectjrtartifactId> <version>1.8.0version> dependency> <dependency> <groupId>org.aspectjgroupId> <artifactId>aspectjweaverartifactId> <version>1.8.0version> dependency> <dependency> <groupId>mysqlgroupId> <artifactId>mysql-connector-javaartifactId> <version>5.1.40version> dependency> <dependency> <groupId>com.microsoft.sqlservergroupId> <artifactId>sqljdbc4artifactId> <version>4.0version> dependency>sqlserver记得要手动下载添加依赖哦
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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:cache="http://www.springframework.org/schema/cache" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.2.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <cache:annotation-driven cache-manager="cacheManager" /> <bean id="cacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"> <property name="configLocation" value="classpath:ehcache.xml" /> bean> <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"> <property name="cacheManager" ref="cacheManagerFactory" /> bean> <context:component-scan base-package="com.test.controller,com.test.service" /> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="100000000"/> <property name="defaultEncoding" value="UTF-8"/> bean> <mvc:annotation-driven> <mvc:message-converters> <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=UTF-8value> <value>application/json;charset=UTF-8value> list> property> <property name="features"> <array> <value>WriteMapNullValuevalue> <value>WriteNullStringAsEmptyvalue> array> property> bean> mvc:message-converters> mvc:annotation-driven> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location"> <value>/WEB-INF/classes/config.propertiesvalue> property> <property name="fileEncoding" value="utf-8" /> bean> <bean id="dataSource_daka" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="initialSize" value="1" /> <property name="minIdle" value="1" /> <property name="maxActive" value="20" /> <property name="maxWait" value="60000" /> <property name="timeBetweenEvictionRunsMillis" value="60000" /> <property name="minEvictableIdleTimeMillis" value="300000" /> <property name="validationQuery" value="SELECT 'x'" /> <property name="testWhileIdle" value="true" /> <property name="testOnBorrow" value="false" /> <property name="testOnReturn" value="false" /> <property name="poolPreparedStatements" value="false" /> <property name="maxPoolPreparedStatementPerConnectionSize" value="20" /> bean> <bean id="dataSource_kaoqin" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${sql.url}" /> <property name="username" value="${sql.username}" /> <property name="password" value="${sql.password}" /> <property name="connectionProperties" value="com.microsoft.sqlserver.jdbc.SQLServerDriver">property> <property name="initialSize" value="1" /> <property name="minIdle" value="1" /> <property name="maxActive" value="20" /> <property name="maxWait" value="60000" /> <property name="timeBetweenEvictionRunsMillis" value="60000" /> <property name="minEvictableIdleTimeMillis" value="300000" /> <property name="validationQuery" value="SELECT 'x'" /> <property name="testWhileIdle" value="true" /> <property name="testOnBorrow" value="false" /> <property name="testOnReturn" value="false" /> <property name="poolPreparedStatements" value="false" /> <property name="maxPoolPreparedStatementPerConnectionSize" value="20" /> bean> <bean id="dynamicDataSource" class="com.dataSourcer.ThreadLocalRountingDataSource"> <property name="defaultTargetDataSource" ref="dataSource_daka"/> <property name="targetDataSources"> <map key-type="com.dataSourcer.DataSources"> <entry key="daka" value-ref="dataSource_daka">entry> <entry key="kaoqin" value-ref="dataSource_kaoqin">entry> map> property> bean> <aop:aspectj-autoproxy expose-proxy="true" proxy-target-class="true" /> <bean id="dynamicDataSourceAspect" class="com.dataSourcer.DynamicDataSourceAspect">bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dynamicDataSource" /> <property name="mapperLocations"> <list> <value>classpath:com/test/mapper/*.xmlvalue> list> property> bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.test.mapper">property> bean> <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dynamicDataSource">property> bean> <tx:annotation-driven transaction-manager="transactionManager"/> beans>
package com.dataSourcer; /** * 编写枚举类,表示数据源的key * */ public enum DataSources { daka, kaoqin }
package com.dataSourcer; /** * 编写线程安全的数据源切换类DataSourceTypeManager * */ public class DataSourceTypeManager { // ThreadLocal类是实现线程安全的关键,因为数据操作大部分都是并发执行,所以必须要考虑线程安全 private static final ThreadLocaldataSourceTypes = new ThreadLocal () { @Override protected DataSources initialValue() { return DataSources.daka; } }; public static DataSources get() { return dataSourceTypes.get(); } public static void set(DataSources dataSourceType) { dataSourceTypes.set(dataSourceType); } public static void reset() { dataSourceTypes.set(DataSources.daka); } public static void clear() { dataSourceTypes.remove(); } }
package com.dataSourcer; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * 扩展类AbstractRoutingDataSource * */ public class ThreadLocalRountingDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceTypeManager.get(); } }
package com.dataSourcer; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 编写自定义注解类 * */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface TargetDataSource { DataSources dataSourceKey() default DataSources.daka; }
package com.dataSourcer; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.lang.reflect.Method; 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; /** * 编写数据源切换切面类:DynamicDataSourceAspect * */ @Aspect @Order(-1) @Component public class DynamicDataSourceAspect { @Pointcut("execution(* com.test.service.*.*(..))") public void pointCut() { } /** * 执行方法前更换数据源 * * @param joinPoint 切点 * @param targetDataSource 动态数据源 */ @Before("@annotation(targetDataSource)") public void doBefore(JoinPoint joinPoint, TargetDataSource targetDataSource) { DataSources dataSourceKey = targetDataSource.dataSourceKey(); if (dataSourceKey == DataSources.kaoqin) { DataSourceTypeManager.set(DataSources.kaoqin); } else { DataSourceTypeManager.set(DataSources.daka); } } /** * 执行方法后清除数据源设置 * * @param joinPoint 切点 * @param targetDataSource 动态数据源 */ @After("@annotation(targetDataSource)") public void doAfter(JoinPoint joinPoint, TargetDataSource targetDataSource) { DataSourceTypeManager.clear();; } @Before(value = "pointCut()") public void doBeforeWithSlave(JoinPoint joinPoint) { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); //获取当前切点方法对象 Method method = methodSignature.getMethod(); if (method.getDeclaringClass().isInterface()) {//判断是否为接口方法 try { //获取实际类型的方法对象 method = joinPoint.getTarget().getClass() .getDeclaredMethod(joinPoint.getSignature().getName(), method.getParameterTypes()); } catch (NoSuchMethodException e) { } } if (null == method.getAnnotation(TargetDataSource.class)) { DataSourceTypeManager.set(DataSources.daka); } } }
最后的最后,在Service中使用方式:
package com.test.service; import java.util.HashMap; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.dataSourcer.DataSources; import com.dataSourcer.TargetDataSource; import com.test.mapper.KaoqinSqlserverMapper; @Service public class KaoqinSqlserverService {
@Autowired private KaoqinSqlserverMapper kaoqin; /** 员工最近10个月考勤情况汇总 */ @TargetDataSource(dataSourceKey = DataSources.kaoqin) public List> userkaoqin_all(String emplid){ return kaoqin.userkaoqin_all(emplid); } /** 员工月度考勤详情 */ @TargetDataSource(dataSourceKey = DataSources.kaoqin) public List > userkaoqin_details(String emplid, String minday, String maxday){ return kaoqin.userkaoqin_details(emplid, minday, maxday); } }
下面这个文章是在Controller中调用的,俺没有测试,有兴趣的可以看看:https://www.cnblogs.com/haha12/p/10613549.html