Java, 原创, 服务器Aop, Aspect, Druid, Spring Boot
Spring boot 使用druid 多数据源 读写分离配置与调用 注解方式
- by chenxue4076
- 4 years ago
分步骤,1配置多数据库,2.数据库调用,3.注解,4.AOP注入读写方式
目录结构如下, AOP的另一篇 注入 环绕通知 请求参数,返回结果统一处理
-aop
--- MultiResponseAspect.java
-common
--- druid
----- DataSourceChoose.java
----- DataSourceGroup.java
----- DynamicDataSource.java
----- DynamicDataSourceHelper.java
----- SpringUtil.java
-config
--- DataSourceConfiguration.java
1.配置:
zeroDateTimeBehavior=CONVERT_TO_NULL 作用是日期为null的时候不报错自动转成 0000-00-00 00:00:00, type需要使用 alibaba的 DruidDataSource, 需要有 druid 的 key
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
master:
pool-name: master
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?zeroDateTimeBehavior=CONVERT_TO_NULL
username: test
password: test
maximum-pool-size: 20
minimun-idle: 5
slave:
pool-name: slave
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?zeroDateTimeBehavior=CONVERT_TO_NULL
username: test
password: test
maximum-pool-size: 20
minimun-idle: 5
2.创建相关类和配置
2.1 添加枚举类 DataSourceGroup.java,这里是 MASTER 和 SLAVE
package cn.test.fcs.common.druid;
/**
* Create By test<chenxue4076@163.com>
* File Name DataSourceGroup.java
* Created Date 2020-12-10
* Created Time 14:35
*/
public enum DataSourceGroup {
//写
MASTER,
//读
SLAVE
}
// end DataSourceGroup.java
//end file
2.2 添加动态加载数据源类 DynamicDataSource
package cn.test.fcs.common.druid;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.Map;
/**
* Create By test<chenxue4076@163.com>
* File Name DynamicDataSource.java
* Created Date 2020-12-10
* Created Time 14:50
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* targetDataSources:保存了key和数据库连接的映射关系
* defaultTargetDataSource:表示默认的数据库连接
*/
public DynamicDataSource(DataSource defaultTargetDataSource, Map targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Override
public Object determineCurrentLookupKey() {
return DynamicDataSourceHelper.getDataSourceGroup();
}
}
// end DynamicDataSource.java
//end file
2.3 添加动态设置和选择读写库的类,DynamicDataSourceHelper
package cn.test.fcs.common.druid;
/**
* Create By test<chenxue4076@163.com>
* File Name DynamicDataSourceHelper.java
* Created Date 2020-12-10
* Created Time 14:55
*/
public class DynamicDataSourceHelper {
private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置数据源的变量
*/
public static void setDataSourceGroup(String dsGroup) {
CONTEXT_HOLDER.set(dsGroup);
}
/**
* 获得数据源的变量
*/
public static String getDataSourceGroup() {
return (String) CONTEXT_HOLDER.get();
}
/**
* 清空数据源变量
*/
public static void clearDataSourceGroup() {
CONTEXT_HOLDER.remove();
}
}
// end DynamicDataSourceHelper.java
//end file
2.4 添加一个获取Bean的类 SpringUtil, 可以根据字符串,class等获取
package cn.test.fcs.common.druid;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* Create By test<chenxue4076@163.com>
* File Name SpringUtil.java
* Created Date 2020-12-10
* Created Time 15:07
*/
@Component
public class SpringUtil implements ApplicationContextAware {
@Autowired
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContextParam) throws BeansException {
applicationContext = applicationContextParam;
}
public Object getObject(String id) {
Object object = null;
object = applicationContext.getBean(id);
return object;
}
public <T> T getObject(Class<T> tClass) {
return applicationContext.getBean(tClass);
}
public Object getBean(String tClass) {
return applicationContext.getBean(tClass);
}
public <T> T getBean(String str,Class<T> tClass) {
return (T)applicationContext.getBean(str);
}
public <T> T getBean(Class<T> tClass) {
return applicationContext.getBean(tClass);
}
}
// end SpringUtil.java
//end file
2.5 添加一个注解接口,用于执行SQL时选择读库还是写库
package cn.test.fcs.common.druid;
import java.lang.annotation.*;
/**
* Create By test<chenxue4076@163.com>
* File Name DataSourceChoose.java
* Created Date 2020-12-10
* Created Time 15:01
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSourceChoose {
DataSourceGroup value() default DataSourceGroup.SLAVE;
}
// end DataSourceChoose.java
//end file
2.6 数据库配置与选择的类 DataSourceConfiguration 主角
package cn.test.fcs.config;
import cn.test.fcs.common.druid.SpringUtil;
import cn.test.fcs.common.druid.DataSourceGroup;
import cn.test.fcs.common.druid.DynamicDataSource;
import cn.test.fcs.common.druid.DynamicDataSourceHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Configuration
@ComponentScan(value = {"com.alibaba.druid.pool","DruidDataSource"})
public class DataSourceConfiguration {
//private static Logger logger = LoggerFactory.getLogger(DataSourceConfiguration.class);
@Autowired
private SpringUtil springUtil;
@Value("${spring.datasource.type}")
private Class<? extends DataSource> dataSourceType;
/**
* master 数据源
*/
@Bean(name = "masterDataSource", destroyMethod = "close", initMethod = "init")
@ConfigurationProperties(prefix = "spring.datasource.druid.master")
public DataSource masterDataSource() {
log.info("-------------------Write Data Source Init----------------");
return DataSourceBuilder.create().type(dataSourceType).build();
}
/**
* slave 数据源
*/
@Bean(name = "slaveDataSource", destroyMethod = "close", initMethod = "init")
@ConfigurationProperties(prefix = "spring.datasource.druid.slave")
@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
public DataSource slaveDataSource() {
log.info("-------------------Read Data Source Init----------------");
return DataSourceBuilder.create().type(dataSourceType).build();
}
@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource dataSource (DataSource slaveDataSource) {
log.info("-------------------dynamicDataSource Init----------------");
Map targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceGroup.SLAVE.name(), slaveDataSource);
//设置备用
setDataSource(targetDataSources, DataSourceGroup.MASTER.name(), "masterDataSource");
//设置默认组
DynamicDataSourceHelper.setDataSourceGroup(DataSourceGroup.SLAVE.name());
DynamicDataSource dynamicDataSource = new DynamicDataSource(slaveDataSource, targetDataSources);
log.info("------默认------" + dynamicDataSource.determineCurrentLookupKey());
return dynamicDataSource;
}
/**
* 设置数据源
* @param targetDataSources 备选数据源集合
* @param sourceName 数据源名称
* @param beanName bean名称
*/
public void setDataSource(Map targetDataSources, String sourceName, String beanName) {
try {
//DataSource dataSource = (DataSource) SpringUtil.getBean(beanName);
DataSource dataSource = (DataSource) springUtil.getBean(beanName);
targetDataSources.put(sourceName, dataSource);
} catch (Exception e) {
log.error(e.getMessage());
e.printStackTrace();
}
}
/**
* 这里的list是多个从库的情况下为了实现简单负载均衡
* @return
* @throws SQLException
*/
/*@Bean("readDataSources")
public List<DataSource> readDataSources() throws SQLException {
List<DataSource> dataSources=new ArrayList<>();
dataSources.add(readDataSourceOne());
return dataSources;
}*/
}
3. 动态调用读写库,在mapper的方法中使用
package cn.test.fcs.crowdsource.gatewayimpl.database;
import cn.test.fcs.common.druid.DataSourceChoose;
import cn.test.fcs.common.druid.DataSourceGroup;
import cn.test.fcs.crowdsource.gatewayimpl.database.dataobject.SupplierDO;
import org.apache.ibatis.annotations.*;
import java.util.List;
import java.util.Map;
@Mapper
public interface CdSupplierMapper {
String TABLE_NAME = "crowdsource_supplier";
String ALL_COLUMNS = "id, supplier_name, supplier_short_name, contact_user, contact_mobile, contact_address, cooperate_start_date, " +
"cooperate_end_date, note, insert_time, update_time, status, creator, modifier";
@DataSourceChoose(value = DataSourceGroup.MASTER)
//使用写库
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
@Insert("<script> " +
"INSERT INTO " + TABLE_NAME + " (" + ALL_COLUMNS + ") VALUES ( null, #{supplierName}, #{supplierShortName}, #{contactUser}, #{contactMobile}, " +
"#{contactAddress}, #{cooperateStartDate}, #{cooperateEndDate}, #{note}, now(), now(), 1, #{creator}, '') " +
"</script>")
int insertUseGeneratedKeys(SupplierDO supplierDO);
@DataSourceChoose(value = DataSourceGroup.SLAVE)
//使用读库或者不写
@Select("SELECT " + ALL_COLUMNS + " FROM " + TABLE_NAME + " WHERE id = #{id} LIMIT 1")
SupplierDO getById(@Param("id") int id);
//不写使用哪种库,在AOP中设置默认为读库
@MapKey("id")
@Select("<script>" +
"SELECT " + ALL_COLUMNS + " FROM " + TABLE_NAME + " WHERE id in " +
"<if test='ids != null'>" +
" <foreach item='item' index='index' collection='ids' open='(' separator=',' close=')'>" +
" #{item} " +
" </foreach>" +
"</if>" +
"</script>")
Map<Integer, SupplierDO> findByIds(@Param("ids") List<Integer> ids);
}
4. AOP 劫持 动态修改读写配置
package cn.test.fcs.aop;
import cn.test.fcs.common.Redis;
import cn.test.fcs.common.druid.DataSourceChoose;
import cn.test.fcs.common.druid.DataSourceGroup;
import cn.test.fcs.common.druid.DynamicDataSourceHelper;
import com.alibaba.cola.dto.MultiResponse;
import com.alibaba.cola.dto.Response;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
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.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* Create By test<chenxue4076@163.com>
* File Name MultiResponseAspect.java
* Created Date 2020/11/24
* Created Time 15:52
*/
@Slf4j
@Component
@Aspect
public class MultiResponseAspect {
//数据库环绕通知
@Pointcut("execution(* cn.test.fcs.*.gatewayimpl.database.*Mapper.*(..))")
public void aroundMapper() {}
//数据库环绕通知
@Around("aroundMapper()")
public Object myAroundMapper(ProceedingJoinPoint proceedingJoinPoint) {
//前置通知
System.out.println("数据库前置通知在这里显示");
//切换数据库
DataSourceChoose dataSourceChoose = getDataSourceChoose(proceedingJoinPoint);
if (dataSourceChoose != null) {
DynamicDataSourceHelper.setDataSourceGroup(dataSourceChoose.value().name());
} else {
//设置默认为读库
DynamicDataSourceHelper.setDataSourceGroup(DataSourceGroup.SLAVE.name());
}
//设置默认结果
Object proceed = null;
try{
//方法执行
System.out.println("数据库方法执行");
log.info("数据库方法执行------使用数据库------" + DynamicDataSourceHelper.getDataSourceGroup());
proceed = proceedingJoinPoint.proceed();
} catch (Throwable e) {
log.error(e.getMessage());
System.out.println(e.getMessage());
return e;
} finally {
//最终通知
System.out.println("数据库最终通知");
DynamicDataSourceHelper.clearDataSourceGroup();
return proceed;
}
}
/**
* 获取需要切换的数据源
*/
public DataSourceChoose getDataSourceChoose(ProceedingJoinPoint proceedingJoinPoint) {
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
Class targetClass = proceedingJoinPoint.getTarget().getClass();
//System.out.println("getDataSourceChoose targetClass " + targetClass.getName());
DataSourceChoose targetDataSource = (DataSourceChoose)targetClass.getAnnotation(DataSourceChoose.class);
//System.out.println("targetDataSource DataSourceChoose " + targetDataSource);
if (targetDataSource != null) {
//System.out.println("targetDataSource has info " + targetDataSource.toString());
return targetDataSource;
} else {
Method method = signature.getMethod();
System.out.println("dataSourceChoose method " + method.getName());
DataSourceChoose dataSourceChoose = method.getAnnotation(DataSourceChoose.class);
System.out.println("dataSourceChoose info " + method.getName() + " " + dataSourceChoose);
return dataSourceChoose;
}
}
}
// end MultiResponseAspect.java
//end file
正常来讲 通过上面几步就完成了读写分离的功能了。
(190)