背景:在以往的项目中,存在这样的一个需求:没有实体(entity、pojo)的情况下,进行表的操作,比如查询某某表数据;以往的处理方式,只需要数据源的切换即可;现在的spring boot +jpa项目中,遇到一个问题:项目中大多数采用的是分包的方式进行数据源的注入(扫包方式),对于没有实体的数据源,便无法进行curd操作,原因是切换数据源失败;为了解决这个切换数据的问题,我们研究了网上不少案例,发现jpa是高度封装的一种多功能工具,切换数据源这伙,臣真的办不到啊(至少现在找不到办法),于是退而研究Jdbc Template,原因是jpa的底层也无非是对JdbcTemplate进行了高度封装处理。通过动态修改JdbcTemplate进行切换数据源的目的。具体步骤如下:
第一步:数据源整理
package com.app.wii.datasources;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import com.app.base.framework.jpa.support.BaseJpaRepositoryFactoryBean;
/**
* base的数据和事物配置
*
* @Date 2018年8月1日
* @author pangxianhe
*
*/
@Configuration // 声名为配置类
@EnableTransactionManagement
@EnableJpaRepositories(
// 指定EntityManager的创建工厂Bean
entityManagerFactoryRef = "entityManagerFactoryBase",
// 指定事物管理的Bean
transactionManagerRef = "transactionManagerBase",
// 设置Repository所在位置
basePackages = { "com.app.base.modules" },
// 覆盖SpringBoot提供的默认配置
repositoryFactoryBeanClass = BaseJpaRepositoryFactoryBean.class)
public class BaseSourceConfig {
@Autowired
@Qualifier("baseDataSource")
private DataSource baseDataSource;
@Primary
@Bean(name = "entityManagerBase")
public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
return entityManagerFactoryBase(builder).getObject().createEntityManager();
}
/**
* 配置实体管理工厂Bean
* @param builder
* @return
* @author pangxianhe
* @date 2018年8月2日
*/
@Primary
@Bean(name = "entityManagerFactoryBase")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBase(EntityManagerFactoryBuilder builder) {
return builder.dataSource(baseDataSource)
.properties(getVendorProperties())
// 设置实体类所在位置
.packages("com.app.base.modules")
.persistenceUnit("basePersistenceUnit")
.build();
}
@Autowired
private JpaProperties jpaProperties;
/**
* 拿到hibernate的通用配置
* @return
* @author pangxianhe
* @date 2018年8月2日
*/
private Map<String, Object> getVendorProperties() {
return jpaProperties.getHibernateProperties(new HibernateSettings());
}
/**
* 配置事物管理的Bean
* @param builder
* @return
* @author pangxianhe
* @date 2018年8月2日
*/
@Primary
@Bean(name = "transactionManagerBase")
public PlatformTransactionManager transactionManagerBase(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(entityManagerFactoryBase(builder).getObject());
}
/**
* 向spring容器base数据源JdbcTemplate
* @param dataSource
* @return
* @author pangxianhe
* @date 2018年11月27日
*/
@Bean(name = "baseDataSourceJdbcTemplate")
public JdbcTemplate primaryJdbcTemplate(@Qualifier("baseDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
base数据,为主数据源,其中需要注意的是这个方法加载进容器中:primaryJdbcTemplate
package com.app.wii.datasources;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import com.app.base.framework.jpa.support.BaseJpaRepositoryFactoryBean;
/**
* wii的数据和事物配置
* @Date 2018年8月1日
* @author pangxianhe
*/
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
// 指定EntityManager的创建工厂Bean
entityManagerFactoryRef = "entityManagerFactoryWii",
// 指定事物管理的Bean
transactionManagerRef = "transactionManagerWii",
// 设置Repository所在位置
basePackages = { "com.app.wii.modules" },
// 覆盖SpringBoot提供的默认配置
repositoryFactoryBeanClass = BaseJpaRepositoryFactoryBean.class)
public class WiiSourceConfig {
@Autowired
@Qualifier("wiiDataSource")
private DataSource wiiDataSource;
@Bean(name = "entityManagerWii")
public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
return entityManagerFactoryWii(builder).getObject().createEntityManager();
}
/**
* 配置实体管理工厂Bean
* @param builder
* @return
* @author pangxianhe
* @date 2018年8月2日
*/
@Bean(name = "entityManagerFactoryWii")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryWii(EntityManagerFactoryBuilder builder) {
return builder.dataSource(wiiDataSource)
.properties(getVendorProperties())
.packages("com.app.wii.modules") // 设置实体类所在位置
.persistenceUnit("wiiPersistenceUnit").build();
}
@Autowired
private JpaProperties jpaProperties;
/**
* 拿到hibernate的通用配置
* @return
* @author pangxianhe
* @date 2018年8月2日
*/
private Map<String, Object> getVendorProperties() {
return jpaProperties.getHibernateProperties(new HibernateSettings());
}
/**
* 配置事物管理的Bean
* @param builder
* @return
* @author pangxianhe
* @date 2018年8月2日
*/
@Bean(name = "transactionManagerWii")
PlatformTransactionManager transactionManagerWii(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(entityManagerFactoryWii(builder).getObject());
}
/**
* 向spring容器wii数据源JdbcTemplate
* @param dataSource
* @return
* @author pangxianhe
* @date 2018年11月27日
*/
@Bean(name = "wiiDataSourceJdbcTemplate")
public JdbcTemplate primaryJdbcTemplate(@Qualifier("wiiDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
wii数据源
package com.app.wii.datasources;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
import com.alibaba.druid.pool.DruidDataSource;
import com.app.wii.properties.ActivitiDbProperties;
import com.app.wii.properties.BaseDbProperties;
import com.app.wii.properties.WiiDbProperties;
/**
* 数据库连接属性配置
* @date 2018年10月24日
* @author pangxianhe
*
*/
@Configuration
@ServletComponentScan
public class DruidDataSourceConfig {
@Autowired
private BaseDbProperties baseDbProperties;
@Autowired
private WiiDbProperties wiiDbProperties;
@Autowired
private ActivitiDbProperties activitiDbProperties;
/**
* base的配置
*/
@Bean
@ConfigurationProperties(prefix = "spring.datasource.base") // 取application.yml文件内配置数据源的前缀
public BaseDbProperties baseDbProperties() {
return new BaseDbProperties();
}
/**
* wii的配置
*/
@Bean
@ConfigurationProperties(prefix = "spring.datasource.wii")
public WiiDbProperties wiiDbProperties() {
return new WiiDbProperties();
}
/**
* activiti的配置
*/
@Bean
@ConfigurationProperties(prefix = "spring.datasource.activiti")
public ActivitiDbProperties activitiDbProperties() {
return new ActivitiDbProperties();
}
/**
* base数据源
* @return
* @author pangxianhe
* @date 2018年8月1日
*/
@Bean(name = "baseDataSource") // 装配该方法返回值为userDataSource管理bean
@Qualifier("baseDataSource") // spring装配bean唯一标识
///@Primary // 通过@Primary表示主数据源。
public DataSource baseDataSource() {
DruidDataSource dataSource = new DruidDataSource();
baseDbProperties.getDruidDataSource(dataSource);
return dataSource;
}
/**
* wii数据源
* @return
* @author pangxianhe
* @date 2018年8月1日
*/
@Bean(name = "wiiDataSource")
@Qualifier("wiiDataSource")
public DataSource wiiDataSource() {
DruidDataSource dataSource = new DruidDataSource();
wiiDbProperties.getDruidDataSource(dataSource);
return dataSource;
}
/**
* activiti数据源
* @return
* @date 2018年8月7日
*/
@Bean(name = "activitiDataSource")
@Qualifier("activitiDataSource")
public DataSource activitiDataSource() {
DruidDataSource dataSource = new DruidDataSource();
activitiDbProperties.getDruidDataSource(dataSource);
return dataSource;
}
/**
* 配置动态数据源组合
* @param baseDataSource
* @param wiiDataSource
* @return
* @author pangxianhe
* @date 2018年11月27日
*/
@Bean(name = "multipleDataSource")
@Qualifier("multipleDataSource")
@Primary
public DataSource MultipleDataSourceToChoose(@Qualifier("baseDataSource") DataSource baseDataSource,
@Qualifier("wiiDataSource") DataSource wiiDataSource) {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceNames.BASE, baseDataSource);
targetDataSources.put(DataSourceNames.WII, wiiDataSource);
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.setDefaultTargetDataSource(wiiDataSource);
dynamicDataSource.afterPropertiesSet();
DynamicDataSource.saveDataSourceTypeName(DataSourceNames.BASE);
DynamicDataSource.saveDataSourceTypeName(DataSourceNames.WII);
return dynamicDataSource;
}
/**
* 向spring容器暴露组合数据源JdbcTemplate
* @param dataSource
* @return
* @author pangxianhe
* @date 2018年11月27日
*/
@Bean(name = "multipleDataJdbcTemplate")
public JdbcTemplate primaryJdbcTemplate(@Qualifier("multipleDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
动态(代理)数据源组装
第二步:数据切换方法编辑
package com.app.wii.datasources;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 切换数据源注解
* @author pangxianhe
* @date 2018年11月27日
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChangDataSource {
/**
* 自定义注解,通过注解可以切换数据源,默认数据源为base数据源
* @return
* @author pangxianhe
* @date 2018年11月27日
*/
String name() default DataSourceNames.BASE;
}
自定义注解:进行切换数据源
package com.app.wii.datasources;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
*
* 多数据源切面处理类
* @date 2018年11月22日
* @author pangxianhe
*/
@Aspect
@Order(-10) //设置容器加载完成之前进行加载
@Component
public class DataSourceAspect {
Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);
/**
* 前置通知
* @param point
* @param source
* @throws Exception
* @author pangxianhe
* @date 2018年11月27日
*/
@Before(value = "@annotation(source)")
public void changeDataSource(JoinPoint point, ChangDataSource source) throws Exception {
String name = source.name();
logger.info("change dataSource :" + name);
if (!DynamicDataSource.checkDataSourceType(name)) {
throw new Exception("数据源"+name+"不存在,使用默认数据源 ");
}
DynamicDataSource.setDataSourceType(name);
}
/**
* 后置通知
* @param point
* @param source
* @throws Exception
* @author pangxianhe
* @date 2018年11月27日
*/
@AfterReturning(value = "@annotation(source)")
public void restoreDataSource(JoinPoint point, ChangDataSource source) {
// 方法执行完毕之后,销毁当前数据源信息,进行垃圾回收。
DynamicDataSource.clearDataSourceType();
}
}
数据源切面,保证在运行时进行数据的切换
package com.app.wii.datasources;
/**
*
* 增加多数据源,在此配置
* @date 2018年11月22日
* @author pangxianhe
*/
public interface DataSourceNames {
/**
* base数据源
*/
String BASE = "baseDataSource";
/**
* wii数据源
*/
String WII = "wiiDataSource";
}
package com.app.wii.datasources;
import java.util.ArrayList;
import java.util.List;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**切换数据源方法
* @date 2018年11月22日
* @author pangxianhe
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
*/
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
/**
* 校验输入的数据库名称是否正确
*/
private static List<String> dataSourceList=new ArrayList<>();
/**
* 使用setDataSourceType设置当前的
* @param dataSourceType
*/
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return contextHolder.get()==null?DataSourceNames.BASE:contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
public static void saveDataSourceTypeName(String name){
dataSourceList.add(name);
}
public static boolean checkDataSourceType(String name){
return dataSourceList.contains(name);
}
@Override
protected Object determineCurrentLookupKey() {
return getDataSourceType();
}
}
数据源切换具体方法
第三步,提供常用的方法,建议,如果可以的请尽量用jpa提供的方法
package com.app.base.framework.jpa.service;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
/**
* @FileName BaseJdbcTemplateService.java
* @Description: 1、由于jpa高度封装,分包指定数据源之后没办法进行数据源的切换
* 2、如果存在实体的情况建议调用jpa原有的方法,不存在实体的情况下可以调用本类方法获取数据源
* 3、本类提供常用方法,如果需要个性定制方法,请通过queryJdbcTemplate获取数据源进行处理
* 4、如果需要原生的JdbcTemp方法,请如下引用
* @Qualifier("multipleDataJdbcTemplate")
@Autowired
private JdbcTemplate jdbcTemplate;
* @Date 2018年11月27日
* @author pangxianhe
*
*/
@Service
public abstract interface BaseJdbcTemplateService<T, ID extends Serializable> {
/**
* 根据sql查询list数据 ,默认base数据源,调用前请确认数据源
* @param sql
* @return
* @throws Exception
* @author pangxianhe
* @date 2018年11月27日
*/
public List<Map<String, Object>> queryListBySql(String sql) throws Exception;
/**
* 根据sql查询Map数据 ,默认base数据源,调用前请确认数据源
* @param sql
* @return
* @throws Exception
* @author pangxianhe
* @date 2018年11月27日
*/
public Map<String, Object> queryMapBySql(String sql) throws Exception;
/**
* 获取数据源JdbcTemplate,通过JdbcTemplate进行数据操作
* @return
* @throws Exception
* @author pangxianhe
* @date 2018年11月27日
*/
public JdbcTemplate queryJdbcTemplate() throws Exception;
/**
* 执行sql
* @param sql
* @throws Exception
* @author pangxianhe
* @date 2018年11月27日
*/
public void execute(String sql) throws Exception;
/**
* 根据sql获取对象
* @param sql
* @param rowMapper 如果存在实体具体操作如下,否则建议采用queryForList 取第一条
* RowMapper<DemoUser> rowMapper = new BeanPropertyRowMapper<DemoUser>(DemoUser.class);
* @return
* @throws Exception
* @author pangxianhe
* @date 2018年11月27日
*/
public <T> T queryObjectBySql(String sql,RowMapper<T> rowMapper) throws Exception;
}
package com.app.base.framework.jpa.service;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
import com.app.base.kernel.model.exception.ServiceException;
import com.app.tool.core.lang.AssertUtil;
/**
* @FileName BaseJdbcTemplateServiceImpl.java
* @Description:
*
* @Date 2018年11月27日
* @author pangxianhe
*
*/
@Service
public class BaseJdbcTemplateServiceImpl<T, ID extends Serializable> implements BaseJdbcTemplateService<T, ID> {
@Qualifier("multipleDataJdbcTemplate")
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public List<Map<String, Object>> queryListBySql(String sql) throws Exception {
AssertUtil.notNull(sql, "sql must not be null");
try {
return jdbcTemplate.queryForList(sql);
} catch (Exception e) {
throw new ServiceException("Problem invoking method, Cause:" + e.getMessage(), (Throwable) e);
}
}
@Override
public Map<String, Object> queryMapBySql(String sql) throws Exception {
AssertUtil.notNull(sql, "sql must not be null");
try {
return jdbcTemplate.queryForMap(sql);
} catch (Exception e) {
throw new ServiceException("Problem invoking method, Cause:" + e.getMessage(), (Throwable) e);
}
}
@Override
public JdbcTemplate queryJdbcTemplate() throws Exception {
return jdbcTemplate;
}
@Override
public void execute(String sql) throws Exception {
AssertUtil.notNull(sql, "sql must not be null");
try {
jdbcTemplate.execute(sql);
} catch (Exception e) {
throw new ServiceException("Problem invoking method, Cause:" + e.getMessage(), (Throwable) e);
}
}
@Override
public <T> T queryObjectBySql(String sql, RowMapper<T> rowMapper) throws Exception {
AssertUtil.notNull(sql, "sql must not be null");
try {
return jdbcTemplate.queryForObject(sql, rowMapper);
} catch (Exception e) {
throw new ServiceException("Problem invoking method, Cause:" + e.getMessage(), (Throwable) e);
}
}
}
以上实现了常用的具体方法,但是建议,请尽量用jpa提供的方法
第四步:调用方式
@Autowired
private BaseJdbcTemplateService<?, ?> baseJdbcTemplateService;
public List<Map<String, Object>> addUser() throws Exception{
String sql = "SELECT * FROM demo_user LIMIT 10";
List<Map<String, Object>> List = baseJdbcTemplateService.queryListBySql(sql);
sql = "SELECT * FROM demo_user where user_id='07M3PX'";
Map<String, Object> map = baseJdbcTemplateService.queryMapBySql(sql);
System.out.println(JSONArray.toJSONString(List));
System.out.println(JSON.toJSON(map));
/*String sql = "UPDATE demo_user set user_name='朱小丽2' where user_id='07M3PX'";
baseJdbcTemplateService.execute(sql);*/
return null;
}
在service进行引入,调用
@GetMapping(value = "/hi3")
@ApiOperation(value = "多数据源测试", notes = "多数据源测试")
//@ChangDataSource(name = DataSourceNames.WII)
public Result hi3Service() throws Exception {
//多种方式获取,也可以注解注入
SqlCommonService sqlCommonService = (SqlCommonService) ApplicationContextUtil.getBean("sqlCommonService");
//切换数据源
DynamicDataSource.setDataSourceType(DataSourceNames.WII);
System.out.println(DynamicDataSource.getDataSourceType());
List<Map<String, Object>> list = sqlCommonService.addUser();
//demoUserService.queryDemoUser();
return null;
///return Result.success().put("list", list);
}
在control中进切换数据源,本人亲测试没毛病,现并未考虑联合事物控制,因为基本调用的是查询方法,后续如果需要再考虑事物的控制。以上两天想出来的方案,如果有更好的方法,请不吝赐教,谢谢