求个免费网站好人有好报,wordpress展示产品,html页面 wordpress,购物网站的设计与实现整体思路#xff1a;
引入基本依赖SpringBootAopMySqlMyBatislombok在配置文件中配置多个数据源创建数据源配置类用于读取配置编写用于标识切换数据源的注解创建数据源切换工具类DataSourceContextHolder编写切面类用于在注解生效处切换数据源编写配置类#xff0c;加载数据…整体思路
引入基本依赖SpringBootAopMySqlMyBatislombok在配置文件中配置多个数据源创建数据源配置类用于读取配置编写用于标识切换数据源的注解创建数据源切换工具类DataSourceContextHolder编写切面类用于在注解生效处切换数据源编写配置类加载数据源创建动态数据源类并继承AbstractRoutingDataSource指定使用哪个数据源关键
项目demo gitee地址多数据源动态切换demo
1.引入依赖
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactIdversion2.7.10/version/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactIdversion2.7.10/version/dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion8.0.21/version/dependencydependencygroupIdorg.mybatis.spring.boot/groupIdartifactIdmybatis-spring-boot-starter/artifactIdversion2.1.3/version/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdversion1.18.22/version/dependencydependencygroupIdcom.alibaba/groupIdartifactIddruid/artifactIdversion1.2.20/version/dependency
2.在配置文件中配置多个数据源
这里配置了上海深圳北京3个数据源需要自己创建这3个库multi-shmulti-szmulti-bj
#默认数据源
datasource.defaultsh
#上海库
spring.datasource.sh.urljdbc:mysql://localhost:3306/multi-sh?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneUTC
spring.datasource.sh.driver-class-namecom.mysql.cj.jdbc.Driver
spring.datasource.sh.usernameroot
spring.datasource.sh.password123#深圳库
spring.datasource.sz.urljdbc:mysql://localhost:3306/multi-sz?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneUTC
spring.datasource.sz.driver-class-namecom.mysql.cj.jdbc.Driver
spring.datasource.sz.usernameroot
spring.datasource.sz.password123#北京库
spring.datasource.bj.urljdbc:mysql://localhost:3306/multi-bj?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneUTC
spring.datasource.bj.driver-class-namecom.mysql.cj.jdbc.Driver
spring.datasource.bj.usernameroot
spring.datasource.bj.password123 3.创建数据源配置类用于读取配置
spring获取统一前缀配置需要可以看我之前的文章SpringBoot项目获取统一前缀配置以及获取非确定名称配置
package com.gooluke.datasource;import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;import java.util.Map;/*** author gooluke*/
Setter
Getter
Component
ConfigurationProperties(prefix spring)
public class MultiDataSourceProperties {/*** 这里的datasource是因为配置是spring.datasource.xx.xx,要配置成datasource,这样才会把配置自动映射进来* 分别映射到url、driverClassName、username、password*/private MapString, DataSourceConfig datasource;SetterGetterpublic static class DataSourceConfig {private String url;private String driverClassName;private String username;private String password;}
}4.编写用于标识切换数据源的注解
package com.gooluke.common.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** author gooluke*/
Target({ElementType.METHOD, ElementType.TYPE})
Retention(RetentionPolicy.RUNTIME)
public interface FixedDataSource {String value();/*** 是否需要还原回之前的数据源(拓展)*/boolean needRecover() default false;}5.创建数据源切换工具类DataSourceContextHolder
package com.gooluke.datasource;import com.gooluke.config.DataSourceConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** author gooluke* 将数据源信息存放至ThreadLocal*/
public class DatasourceContextHolder {private static final Logger log LoggerFactory.getLogger(DatasourceContextHolder.class);private static final ThreadLocalString DATASOURCE_THREAD_LOCAL new ThreadLocal();public static void setDatasource(String datasource) {if (datasource ! null DataSourceConfig.dataSources.get(datasource) null) {String errorMsg String.format(数据源[%s]未配置, datasource);log.error(errorMsg);throw new RuntimeException(errorMsg);}DATASOURCE_THREAD_LOCAL.set(datasource);}public static String getDatasource() {return DATASOURCE_THREAD_LOCAL.get();}public static void clearDatasource() {DATASOURCE_THREAD_LOCAL.remove();}
}6.编写切面类用于在注解生效处切换数据源
package com.gooluke.aspect;import com.gooluke.common.annotation.FixedDataSource;
import com.gooluke.datasource.DatasourceContextHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;/*** author gooluke* 切换数据源切面类* 这个已不再使用使用com.gooluke.aop.DataSourceAnnotationAdvisor替代*/
Aspect
Component
public class DataSourceAspect {private static final Logger log LoggerFactory.getLogger(DataSourceAspect.class);/*** 注解加在方法上*/Pointcut(annotation(com.gooluke.common.annotation.FixedDataSource))private void methodPointCut() {}/*** 注解加在方法上*/Pointcut(within(com.gooluke.common.annotation.FixedDataSource))public void classPointcut() {}Around(value methodPointCut() || classPointcut())public Object around(ProceedingJoinPoint joinPoint) throws Throwable {//记录当前数据源和准备切换的数据源String oldDatasource DatasourceContextHolder.getDatasource();MethodSignature methodSignature (MethodSignature) joinPoint.getSignature();java.lang.reflect.Method method methodSignature.getMethod();FixedDataSource annotation method.getAnnotation(FixedDataSource.class);//方法上获取注解为空再从类上获取if (annotation null) {annotation method.getDeclaringClass().getAnnotation(FixedDataSource.class);}String newDatasource annotation.value();//切换数据源并执行操作DatasourceContextHolder.setDatasource(newDatasource);try {return joinPoint.proceed();} finally {//是否切换回初始数据源if (annotation.needRecover()) {DatasourceContextHolder.setDatasource(oldDatasource);}}}
}7.编写配置类加载数据源
这个配置类主要就是将我们配置的多数据源解析然后统一管理dynamicDataSource.setTargetDataSources(targetDataSources); 以及设置默认数据源。
package com.gooluke.config;import com.alibaba.druid.pool.DruidDataSource;
import com.gooluke.aop.DataSourceAnnotationAdvisor;
import com.gooluke.aop.DataSourceAnnotationInterceptor;
import com.gooluke.datasource.DynamicDataSource;
import com.gooluke.datasource.MultiDataSourceProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;/*** author gooluke*/Configuration
Slf4j
public class DataSourceConfig {public static final MapString, String dataSources new HashMap();AutowiredMultiDataSourceProperties dataSourceProperties;Value(${datasource.default:})private String defaultDataSourceName;BeanPrimarypublic DynamicDataSource dynamicDataSource() {DynamicDataSource dynamicDataSource new DynamicDataSource();//存放所有数据源MapObject, Object targetDataSources new HashMap();MapString, MultiDataSourceProperties.DataSourceConfig datasourceMap dataSourceProperties.getDatasource();if (datasourceMap.entrySet().size() 1 (defaultDataSourceName null || defaultDataSourceName.isEmpty())) {throw new RuntimeException(存在多个数据源未配置默认数据源datasource.default);}datasourceMap.forEach((datasourceName, config) - {DataSource dataSource createDataSource(config);targetDataSources.put(datasourceName, dataSource);dataSources.put(datasourceName, datasourceName);log.info(已初始化数据库{}, datasourceName);if (datasourceMap.size() 1 || (defaultDataSourceName ! null !defaultDataSourceName.isEmpty() defaultDataSourceName.equals(datasourceName))) {//这里设置默认数据源dynamicDataSource.setDefaultTargetDataSource(dataSource);log.info(已设置默认数据源: {}, datasourceName);}});//这里把数据源统一管理dynamicDataSource.setTargetDataSources(targetDataSources);return dynamicDataSource;}private DataSource createDataSource(MultiDataSourceProperties.DataSourceConfig dataSourceConfig) {DruidDataSource dataSource new DruidDataSource();dataSource.setUrl(dataSourceConfig.getUrl());dataSource.setDriverClassName(dataSourceConfig.getDriverClassName());dataSource.setUsername(dataSourceConfig.getUsername());dataSource.setPassword(dataSourceConfig.getPassword());dataSource.setValidationQuery(SELECT 1);dataSource.setTestWhileIdle(true);dataSource.setTestOnBorrow(false);dataSource.setTestOnReturn(false);dataSource.setPoolPreparedStatements(true);dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);return dataSource;}BeanPrimarypublic SqlSessionFactory sqlSessionFactory(DynamicDataSource dynamicDataSource) throws Exception {SqlSessionFactoryBean sessionFactory new SqlSessionFactoryBean();sessionFactory.setDataSource(dynamicDataSource);PathMatchingResourcePatternResolver resolver new PathMatchingResourcePatternResolver();org.springframework.core.io.Resource[] resources resolver.getResources(classpath:/mapper/*.xml);//org.springframework.core.io.Resource config resolver.getResource(classpath:mybatis-config.xml);sessionFactory.setMapperLocations(resources);//sessionFactory.setConfigLocation(config);return sessionFactory.getObject();}BeanPrimarypublic DataSourceTransactionManager transactionManager(DynamicDataSource dynamicDataSource) {return new DataSourceTransactionManager(dynamicDataSource);}}8.创建动态数据源类并继承AbstractRoutingDataSource指定使用哪个数据源关键
这里可以理解为就是一个口子让我们自己指定数据源如果你返回的是null则会指定我们配置类中设置的默认数据源dynamicDataSource.setDefaultTargetDataSource(dataSource);
package com.gooluke.datasource;import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/*** author gooluke* 动态数据源*/
Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {/*** 这里返回ThreadLocal中的数据源*/Overrideprotected Object determineCurrentLookupKey() {return DatasourceContextHolder.getDatasource();}}9.请求完成后记得清空ThreadLocal否则会造成内存泄漏
编写一个拦截器在请求完成后remove
package com.gooluke.interceptor;import com.gooluke.datasource.DatasourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** author gooluke*/
Component
Slf4j
public class DataSourceInterceptor implements HandlerInterceptor {Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {DatasourceContextHolder.clearDatasource();log.info(请求处理完成清除数据源);}
}10.代码演示
将注解加在实现类方法上或者加在mapper/dao接口上一般加在这里因为dao接口一般都是操作同一个库这里指定了其它别的方法直接调用即可
10.1 service层
package com.gooluke.service.impl;import com.gooluke.dao.UserInfoDao;
import com.gooluke.dao.UserInfoDao2;
import com.gooluke.entity.TUserInfo;
import com.gooluke.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;/*** author gooluke*/
Service
public class UserServiceImpl implements UserService {Autowiredprivate UserInfoDao userInfoDao;Autowiredprivate UserInfoDao2 userInfoDao2;/*** 在这里没有设置数据源dao层设置了数据源可以自动切换*/Overridepublic ListTUserInfo selectList() {//先查深圳库再查上海库ListTUserInfo tUserInfos userInfoDao.selectUserList(new TUserInfo());tUserInfos.forEach(System.out::println);ListTUserInfo tUserInfos2 userInfoDao2.selectUserList(new TUserInfo());tUserInfos2.forEach(System.out::println);tUserInfos.addAll(tUserInfos2);return tUserInfos;}}10.2 dao层
dao1指定深圳库:
package com.gooluke.dao;import com.gooluke.common.annotation.FixedDataSource;
import com.gooluke.common.constants.DataSourceName;
import com.gooluke.entity.TUserInfo;
import org.apache.ibatis.annotations.Mapper;import java.util.List;/*** author gooluke*/
Mapper
FixedDataSource(DataSourceName.SHENZHEN)
public interface UserInfoDao {ListTUserInfo selectUserList(TUserInfo userInfo);}dao2指定上海库:
package com.gooluke.dao;import com.gooluke.common.annotation.FixedDataSource;
import com.gooluke.common.constants.DataSourceName;
import com.gooluke.entity.TUserInfo;
import org.apache.ibatis.annotations.Mapper;import java.util.List;/*** author gooluke*/
Mapper
FixedDataSource(DataSourceName.SHANGHAI)
public interface UserInfoDao2 {ListTUserInfo selectUserList(TUserInfo userInfo);}10.3 观察结果
切库成功分别查询了不同库的数据并在最后清空了ThreadLocal中的数据 11.动态数据源开源dynamic-datasource-spring-boot-starter
上面这种Aop的实现方式在注解加在service接口的方法上其实是不生效的当然也不建议加在service接口上通常是加在实现类类上或者方法上。而Mapper/Dao接口的实现类是通过mybatis动态代理生成的注解加在Mapper/Dao接口上是能生效的我没有找到为啥他的实现类可以的文章。而我们也可以通过别的方式把注解加在接口上的场景通过Aop拦截只是不建议。下面是开源组件-动态数据源
com.baomidou:dynamic-datasource-spring-boot-starter:3.3.2
的Aop方案有兴趣的可以去看一下他的源码我的工程里也是用的这种方案需要在配置类中声明bean
11.1 创建一个DataSourceAnnotationAdvisor去继承AbstractPointcutAdvisor类并实现BeanFactoryAware接口
11.2 重写getPointcut()、getAdvice()、setBeanFactory()方法
11.3 配置声明Bean Beanpublic DataSourceAnnotationAdvisor dataSourceAnnotationAdvisor() {DataSourceAnnotationInterceptor dataSourceAnnotationInterceptor new DataSourceAnnotationInterceptor();return new DataSourceAnnotationAdvisor(dataSourceAnnotationInterceptor);}