When doing a project, sometimes it is necessary to configure multiple data sources in a project. There are many online too. I will write a more complete and relatively elegant way here.
When using mybatis, you can use multiple DAOs. One DAO does it all
Go directly to the code:
1. First add the required data source configuration
# Data source, Druid is configured by default spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.driverClassName=com.mysql.jdbc.Driver #Multiple data source configuration test.datasource.url=jdbc:mysql://192.168.1.2:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&allowMultiQueries=true test.datasource.username=root test.datasource.password=123456 #Database 2 test2.datasource.url=jdbc:mysql://192.168.1.3:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&allowMultiQueries=true test2.datasource.username=root test2.datasource.password=123456
2. Add a database enumeration
/** * List all data source keys (usually named by database name), which can be changed to your own data source name * Notice: * 1) Here the data source and database are one-to-one *2) The variable name in DatabaseType is the name of the database */ public enum DatabaseType { TEST_DB("TEST_DB"), TEST_DB2("TEST_DB2"); private String code; private DatabaseType(String code) { this.code = code; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } }
3. Add the database identification class, no need to change it, just use it directly
/** * @Title: DataSourceContextHolder * @Description: A key step, an identification, when setting up the dynamic data source, two libraries are connected, but how to determine that each connection is the database that needs to be connected, then this identification is required. * * @author gogym * @date 12:21:31 PM */ public class DataSourceContextHolder { private static final ThreadLocal<DatabaseType> contextHolder = new ThreadLocal<DatabaseType>(); public static void setDbType(DatabaseType dbType) { contextHolder.set(dbType); } public static DatabaseType getDbType() { return contextHolder.get(); } public static void clearDbType() { contextHolder.remove(); } }
4. Add dynamic data source. No need to change, just use
/** * * Dynamic data source * @author gogym * @version April 27, 2018 * @see DynamicDataSource * @since */ public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDbType(); } }
5. Add a custom annotation to specify the data source, you can directly apply it without changing it
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * * Custom annotation to specify the data source * @author gogym * @version April 27, 2018 * @see DS * @since */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface DS { DatabaseType value(); }
6. Add an AOP to parse annotations. Apply directly without changing
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.reflect.MethodSignature; import org.springframework.stereotype.Component; /** * * Custom AOP aspect for parsing annotations * @author gogym * @version April 27, 2018 * @see DynamicDataSourceAspect * @since */ @Aspect @Component public class DynamicDataSourceAspect { @Before("@annotation(DS)") public void beforeSwitchDS(JoinPoint point) { // Get the currently accessed class Class<?> className = point.getTarget().getClass(); // get access method name String methodName = point.getSignature().getName(); // get the type of the method's parameter Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes(); DatabaseType dataSource = DataSourceContextHolder.getDbType(); try { // get access method object Method method = className.getMethod(methodName, argClass); // Determine if there is an @DS annotation if (method.isAnnotationPresent(DS.class)) { DS annotation = method.getAnnotation(DS.class); // Get the data source name in the annotation dataSource = annotation.value(); } } catch (Exception e) { e.printStackTrace (); } // switch data source DataSourceContextHolder.setDbType(dataSource); } @After("@annotation(DS)") public void afterSwitchDS(JoinPoint point) { DataSourceContextHolder.clearDbType(); } }
7. Add a database DAO, and the method of operating the database is basically satisfied. no need for correction
/** * Database operation DAO This is written to write less mapper mapping classes when using myBatis * * @author gogym * @version August 30, 2017 * @see CommonDao * @since */ public interface CommonDao { /** * save object * * @param str * @param obj * @return * @throws Exception */ public Object save(String str, Object obj) throws Exception; /** * Modify the object * * @param str * @param obj * @return * @throws Exception */ public Object update(String str, Object obj) throws Exception; /** * delete object * * @param str * @param obj * @return * @throws Exception */ public Object delete(String str, Object obj) throws Exception; /** * find object * * @param str * @param obj * @return * @throws Exception */ public Object findForObject(String str, Object obj) throws Exception; /** * Find object and return List * * @param str * @param obj * @return * @throws Exception */ public Object findForList(String str, Object obj) throws Exception; /** * The search object is encapsulated into a Map * * @param s * @param obj * @return * @throws Exception */ public Object findForMap(String sql, Object obj, String key, String value) throws Exception; }
8. Add DAO implementation class. no need for correction
import org.mybatis.spring.support.SqlSessionDaoSupport; public class CommonDaoImpl extends SqlSessionDaoSupport implements CommonDao { @Override public Object save(String str, Object obj) throws Exception { return getSqlSession().insert(str, obj); } @Override public Object update(String str, Object obj) throws Exception { return getSqlSession().update(str, obj); } @Override public Object delete(String str, Object obj) throws Exception { return getSqlSession().delete(str, obj); } @Override public Object findForObject(String str, Object obj) throws Exception { return getSqlSession().selectOne(str, obj); } @Override public Object findForList(String str, Object obj) throws Exception { return getSqlSession().selectList(str, obj); } @Override public Object findForMap(String sql, Object obj, String key, String value) throws Exception { return getSqlSession().selectMap(sql, key, value); } }
9. Add project configuration and modify it according to your own needs
import java.util.HashMap; import java.util.Map; import java.util.Properties; import javax.sql.DataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.env.Environment; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import com.alibaba.druid.pool.DruidDataSourceFactory; import com.poly.rbl.dao.CommonDao; import com.poly.rbl.dao.impl.CommonDaoImpl; import com.poly.rbl.datasource.DatabaseType; import com.poly.rbl.datasource.DynamicDataSource; @Configuration public class ApplicationConfig extends WebMvcConfigurerAdapter { // mapper file path, the path where you store the mapper file yourself static final String MAPPER_LOCATION = "classpath:mapper/**/*.xml"; // mybatis configuration file path static final String MYBATIS_CONFIG = "xml/myBatisSetting.xml"; @Autowired private Environment env; /** * Create 2 data sources (the name of the data source: the method name can be taken as XXXDataSource(), XXX is the database name, which is also the name of the data source) */ private DataSource testDBdataSource() throws Exception { Properties props = new Properties(); props.put("driverClassName", env.getProperty("spring.datasource.driverClassName")); props.put("url", env.getProperty("test.datasource.url")); props.put("username", env.getProperty("test.datasource.username")); props.put("password", env.getProperty("test.datasource.password")); return DruidDataSourceFactory.createDataSource(props); } private DataSource testDBdataSource2() throws Exception { Properties props = new Properties(); props.put("driverClassName", env.getProperty("spring.datasource.driverClassName")); props.put("url", env.getProperty("test2.datasource.url")); props.put("username", env.getProperty("test2.datasource.username")); props.put("password", env.getProperty("test2.datasource.password")); return DruidDataSourceFactory.createDataSource(props); } /** * @throws Exception * @Primary This annotation indicates that when there are multiple implementation classes for the same interface that can be injected, which one is selected by default, instead of letting the @autowire annotation report an error * @Qualifier is injected by name, usually in one injection with multiple instances of the same type (e.g. with multiple instances of the DataSource type) */ @Bean("dynamicDataSource") @Primary public DynamicDataSource dynamicDataSource() throws Exception { DataSource testDBdataSource = testDBdataSource(); DataSource testDBdataSource2 = testDBdataSource2(); // Multiple data sources can be added, if needed Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DatabaseType.TEST_DB, testDBdataSource); targetDataSources.put(DatabaseType.TEST_DB2, testDBdataSource2); DynamicDataSource dynamicDataSource = new DynamicDataSource(); dynamicDataSource.setTargetDataSources(targetDataSources);// This method is the method of AbstractRoutingDataSource dynamicDataSource.setDefaultTargetDataSource(testDBdataSource);// The default datasource is set to myTestDbDataSource return dynamicDataSource; } @Bean public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DynamicDataSource dynamicDataSource) throws Exception { SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); /** Set mybatis configuration scan path */ sessionFactory.setConfigLocation(new ClassPathResource(MYBATIS_CONFIG)); /** set datasource */ sessionFactory.setDataSource(dynamicDataSource); /** Add mapper scan path */ sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(ApplicationConfig.MAPPER_LOCATION)); /** Set the typeAlias package scan path */ // sessionFactory.setTypeAliasesPackage("com...."); return sessionFactory.getObject(); } /** * Configure transaction manager */ @Bean public DataSourceTransactionManager transactionManager(DynamicDataSource dynamicDataSource) throws Exception { return new DataSourceTransactionManager(dynamicDataSource); } /** * Inject a dao here. The purpose is to not want to write too many mapper classes, and only need one dao to execute all mybatis sql * * @param sqlSessionFactory * @return * @throws Exception */ @Bean("commonDao") // @Lazy// Note that lazy loading annotations may be added here, otherwise spring will not know which one to load first, which is likely to cause circular dependencies public CommonDao commonDao(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { CommonDaoImpl cd = new CommonDaoImpl(); cd.setSqlSessionFactory(sqlSessionFactory); return cd; } }
At this point, the basic configuration has been completed. It is very simple to use, just add an annotation directly to the service method
@Service public class OrderDeliveryServiceImpl implements OrderDeliveryService { @Autowired private CommonDao commonDao; @Override @DS(DatabaseType.TEST_DB2) //Specify to use test2 data source, if not specified, use test by default public OrderDelivery findByOrderNo(String orderNo) { try { return (OrderDelivery)commonDao.findForObject( MyBatisUtils.getSqlKey(OrderDelivery.class, "selectByOrderNo"), orderNo); } catch (Exception e) { e.printStackTrace (); } return null; } }
/** * Map mapper file * @author gogym * @version 1.0 */ public class MyBatisUtils { private static String getPackageName(Class<?> clzss) { return clzss.getPackage().getName(); } public static String getSqlKey(Class<?> clzss, String shortKey) { return getPackageName(clzss).concat(".").concat(clzss.getSimpleName()).concat(".").concat( shortKey); } }
over.