Spring boot +jpa環境下,多數據源處理,並且在control進行切換數據源

背景:在以往的項目中,存在這樣的一個需求:沒有實體(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中進切換數據源,本人親測試沒毛病,現並未考慮聯合事物控制,因爲基本調用的是查詢方法,後續如果需要再考慮事物的控制。以上兩天想出來的方案,如果有更好的方法,請不吝賜教,謝謝

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章