做项目,有时候需要一个工程里配置多个数据源。网上也有很多啦。我这里写一个比较全,实现相对优雅的方式吧。
使用mybatis的时候,可以不用多个DAO。一个DAO全部搞定
直接上代码:
1、先添加需要的数据源配置
# 数据源,默认配置Druid spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.driverClassName=com.mysql.jdbc.Driver #多数据源配置 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 #数据库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、添加一个数据库枚举
/** * 列出所有的数据源key(常用数据库名称来命名),可以改为你自己的数据源名称 * 注意: * 1)这里数据源与数据库是一对一的 * 2)DatabaseType中的变量名称是数据库的名称 */ 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、添加数据库标识类,不用改,直接用
/** * @Title: DataSourceContextHolder * @Description: 关键的一步,一个标识,在设置动态数据源的时候,连接了两个库,但是在怎 么确定每次连接都是需要连接的数据库呢,那就要这个标识了。 * * @author gogym * @date 下午12:21:31 */ 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、添加动态数据源。不用改,直接用
/** * * 动态数据源 * @author gogym * @version 2018年4月27日 * @see DynamicDataSource * @since */ public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDbType(); } }
5、添加一个自定义注解,用来指定数据源,不用改,可以直接套用
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * * 自定义注解,用来指定数据源 * @author gogym * @version 2018年4月27日 * @see DS * @since */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface DS { DatabaseType value(); }
6、添加一个AOP,用来解析注解。直接套用,不用改
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; /** * * 自定义AOP切面,用来解析注解 * @author gogym * @version 2018年4月27日 * @see DynamicDataSourceAspect * @since */ @Aspect @Component public class DynamicDataSourceAspect { @Before("@annotation(DS)") public void beforeSwitchDS(JoinPoint point) { // 获得当前访问的class Class<?> className = point.getTarget().getClass(); // 获得访问的方法名 String methodName = point.getSignature().getName(); // 得到方法的参数的类型 Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes(); DatabaseType dataSource = DataSourceContextHolder.getDbType(); try { // 得到访问的方法对象 Method method = className.getMethod(methodName, argClass); // 判断是否存在@DS注解 if (method.isAnnotationPresent(DS.class)) { DS annotation = method.getAnnotation(DS.class); // 取出注解中的数据源名 dataSource = annotation.value(); } } catch (Exception e) { e.printStackTrace(); } // 切换数据源 DataSourceContextHolder.setDbType(dataSource); } @After("@annotation(DS)") public void afterSwitchDS(JoinPoint point) { DataSourceContextHolder.clearDbType(); } }
7、添加一个数据库DAO,操作数据库的方法基本满足。不需要修改
/** * 数据库操作DAO 写这个是为了使用myBatis时可以少写一些mapper映射类 * * @author gogym * @version 2017年8月30日 * @see CommonDao * @since */ public interface CommonDao { /** * 保存对象 * * @param str * @param obj * @return * @throws Exception */ public Object save(String str, Object obj) throws Exception; /** * 修改对象 * * @param str * @param obj * @return * @throws Exception */ public Object update(String str, Object obj) throws Exception; /** * 删除对象 * * @param str * @param obj * @return * @throws Exception */ public Object delete(String str, Object obj) throws Exception; /** * 查找对象 * * @param str * @param obj * @return * @throws Exception */ public Object findForObject(String str, Object obj) throws Exception; /** * 查找对象返回List * * @param str * @param obj * @return * @throws Exception */ public Object findForList(String str, Object obj) throws Exception; /** * 查找对象封装成Map * * @param s * @param obj * @return * @throws Exception */ public Object findForMap(String sql, Object obj, String key, String value) throws Exception; }
8、添加DAO实现类。不需要修改
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、添加工程配置,根据你自己的需要修改
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文件路径,你自己存放mapper文件的路径 static final String MAPPER_LOCATION = "classpath:mapper/**/*.xml"; // mybatis 配置文件路径 static final String MYBATIS_CONFIG = "xml/myBatisSetting.xml"; @Autowired private Environment env; /** * 创建2个数据源(数据源的名称:方法名可以取为XXXDataSource(),XXX为数据库名称,该名称也就是数据源的名称) */ 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 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错 * @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例) */ @Bean("dynamicDataSource") @Primary public DynamicDataSource dynamicDataSource() throws Exception { DataSource testDBdataSource = testDBdataSource(); DataSource testDBdataSource2 = testDBdataSource2(); // 可以添加多个数据源,如果需要的话 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);// 该方法是AbstractRoutingDataSource的方法 dynamicDataSource.setDefaultTargetDataSource(testDBdataSource);// 默认的datasource设置为myTestDbDataSource return dynamicDataSource; } @Bean public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DynamicDataSource dynamicDataSource) throws Exception { SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); /** 设置mybatis configuration 扫描路径 */ sessionFactory.setConfigLocation(new ClassPathResource(MYBATIS_CONFIG)); /** 设置datasource */ sessionFactory.setDataSource(dynamicDataSource); /** 添加mapper 扫描路径 */ sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(ApplicationConfig.MAPPER_LOCATION)); /** 设置typeAlias 包扫描路径 */ // sessionFactory.setTypeAliasesPackage("com...."); return sessionFactory.getObject(); } /** * 配置事务管理器 */ @Bean public DataSourceTransactionManager transactionManager(DynamicDataSource dynamicDataSource) throws Exception { return new DataSourceTransactionManager(dynamicDataSource); } /** * 这里注入一个dao。目的是为了不想写太多的mapper类,只需要一个dao就能执行所有的mybatis sql * * @param sqlSessionFactory * @return * @throws Exception */ @Bean("commonDao") // @Lazy// 注意,这里可能要添加延迟加载注解,否则spring不知先加载哪一个,很可能造成循环依赖 public CommonDao commonDao(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { CommonDaoImpl cd = new CommonDaoImpl(); cd.setSqlSessionFactory(sqlSessionFactory); return cd; } }
到这里,基本配置已经完成了。使用非常简单,直接在service方法里面添加一个注解即可
@Service public class OrderDeliveryServiceImpl implements OrderDeliveryService { @Autowired private CommonDao commonDao; @Override @DS(DatabaseType.TEST_DB2) //指定使用test2数据源,不指定默认使用test public OrderDelivery findByOrderNo(String orderNo) { try { return (OrderDelivery)commonDao.findForObject( MyBatisUtils.getSqlKey(OrderDelivery.class, "selectByOrderNo"), orderNo); } catch (Exception e) { e.printStackTrace(); } return null; } }
/** * 映射mapper文件 * @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); } }
完。