前言:本人非计算机专业,因为毕业后的迷茫选择了java,虽然是业余的,但是从不敢放弃继续学习啊。学如逆水行舟,不进则退。希望广大的萌新能热爱这个行业,每天进步一点点,一年后就会很强大了哟~
其他多个数据库的配置也可以参考这里
正事开始:
整合的是SSM整合数据库,我这里是整合了mysql和sqlite,因为这个项目需要一个本地的数据库作为临时存储的数据,项目虽少,但是坑却不少,好在最后客户支付了几万大洋,也是对我们开发者的一种鼓励吧!
会我这个整合数据源,然后双mysql,双oracle,双sqlite,n个mysql,oracle,sqlserver整合都不是难事!
前提:首先先整合好SSM框架,这里省略不计,有关框架的搭建网上有很多的例子!
maven先引入jdbc-sqlite的包:(sqlite的数据库需要自己下载,就几百K,我的是64位版本的)
<!--和sqlite整合jdbc--> <dependency> <groupId>org.xerial</groupId> <artifactId>sqlite-jdbc</artifactId> <version>3.23.1</version> </dependency>
注意:SSM中配置sqlite的数据源的时候一定要写绝对路径,因为写相对路径的话,会发现查询sqlite数据库是可以的,但是插入数据却不可以,这里我花了好久的时间断点才解决。希望有需求的萌新们不要再写相对路径了
这里整合sqlite的数据库连接池,看到我的注释了吗?需要写绝对路径!(大佬可以写一个监听器,在服务启动的时候可以获取这个sqlite的数据库的绝对路径的位置,然后再补上数据源这里也是可以的)
整合sqlite与mysql,看:我这里整合3个数据源都无压力啊!
<!--数据库3sqlite连接池--> <bean id="dataSourceSqlite" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="org.sqlite.JDBC" /> <!--注意:这个相对路径只有在执行DML的时候会有效,执行DDL的时候需要写上绝对地址--> <property name="jdbcUrl" value="jdbc:sqlite:D:\Fapiao\fapiao\wudi.db"/> <property name="initialPoolSize" value="5" /> <property name="minPoolSize" value="1" /> <property name="maxPoolSize" value="10" /> <property name="maxStatements" value="100" /> <property name="maxIdleTime" value="3600" /> <property name="acquireIncrement" value="2" /> <property name="acquireRetryAttempts" value="10" /> <property name="acquireRetryDelay" value="600" /> <property name="testConnectionOnCheckin" value="true" /> <property name="idleConnectionTestPeriod" value="1200" /> <property name="checkoutTimeout" value="10000" /> </bean>
<!-- 数据库mysql1的连接池 --> <bean id="dataSource1" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"/> <property name="jdbcUrl" value="${jdbc.url1}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password1}"/> <property name="maxPoolSize" value="${c3p0.maxPoolSize}"/> <property name="minPoolSize" value="${c3p0.minPoolSize}"/> <property name="autoCommitOnClose" value="${c3p0.autoCommitOnClose}"/> <property name="checkoutTimeout" value="${c3p0.checkoutTimeout}"/> <property name="acquireRetryAttempts" value="${c3p0.acquireRetryAttempts}"/> </bean>
<!--本机数据库mysql2连接池--> <bean id="dataSource2" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"/> <property name="jdbcUrl" value="${jdbc.url2}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password2}"/> <property name="maxPoolSize" value="${c3p0.maxPoolSize}"/> <property name="minPoolSize" value="${c3p0.minPoolSize}"/> <property name="autoCommitOnClose" value="${c3p0.autoCommitOnClose}"/> <property name="checkoutTimeout" value="${c3p0.checkoutTimeout}"/> <property name="acquireRetryAttempts" value="${c3p0.acquireRetryAttempts}"/> </bean>
接下来就是最关键的代码了,SSM中有这样的一个类,只要整合了SSM,这个类就会有的!AbstractRoutingDataSource
//第一个类,我自定义的类,这个类还需要注入到spring中进行配置 public class DynamicDataSource extends AbstractRoutingDataSource{ @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceHolder.getDataSource(); } }
//这是定义设置的第二个类,看方法就可以知道,第一个是得到数据源,第二个是设置数据源,第三个是清除数据源连接,都很重要,都很重要,都很重要,重要的事情说三遍!
public class DynamicDataSourceHolder { //private static final ThreadLocal<String> dataSourceKey = new InheritableThreadLocal<String>(); private static final ThreadLocal<String> dataSourceKey = new ThreadLocal<String>(); public static String getDataSource() { return dataSourceKey.get(); } public static void setDataSource(String dataSource) { dataSourceKey.set(dataSource); } public static void clearDataSource() { dataSourceKey.remove(); } }
然后再到spring中配置一下数据:
<bean id="dataSource" class="com.ilongsay.utils.DynamicDataSource"> <!--默认数据源--> <property name="defaultTargetDataSource" ref="dataSource2"/> <property name="targetDataSources"> <map key-type="java.lang.String"> <!--这里是上面数据源配置的bean id--> <entry key="dataSource1" value-ref="dataSource1"/> <entry key="dataSource2" value-ref="dataSource2"/> <entry key="dataSourceSqlite" value-ref="dataSourceSqlite"/> </map> </property> </bean>
配置到了这里以后,还仅仅只完成了部分。为了更加方便的切换数据源,当然是注解+spring的AOP更加方便了!这里我定义了一个自定义的注解,用来设置数据源。aop就是动态代理的意思,让它代理哪个类,那它就会把这个类一层一层的给剥开,这里我是将数据源的切换设置在服务层上(和之后的spring事务差不多,事务也是动态代理,把之前的类一层一层的剥开,然后加入自己的东西)!这里会有一个大坑,我稍后再说
自定义的注解:
比如这个注解标记在哪个类上,动态代理会把这个类给剥开,自然能找到标记的注解和注解里面的值了,然后再把这个值赋值给相关的变量
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) @Documented public @interface DataSource { String value() default ""; }
//先给你们一睹为快,这个dataSource1就是配置的bean id的数据源,标记在服务层,就能自动的切换数据源
@DataSource("dataSource1") public interface IBillDetailService { public List<FaPiaoDetail> findBillDetailByBillCode(String billCode); }
接下来就是AOP的配置了,这里和配置事务是一样的!这个坑就是有关事务的,记住,启动的时候一定要设置这个aop优先于事务启动,不然会有大坑。导致切换失败,而且各种报错!
注意:这里的这个clearDataSource()必须要设置,必须要设置。如果不设置的话,会导致后面需要切换数据源的服务一直使用前一个数据源,从而达不到效果!
/** * @Author ilongsay * @Email ilongsay@163.com * @Describution aop设置 */
@Component public class DataSwitchAop { /** * 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源 * * @param point * @throws Exception */ public void intercept(JoinPoint point) throws Exception { System.out.println("==========切换数据源================="); Class<?> target = point.getTarget().getClass(); MethodSignature signature = (MethodSignature) point.getSignature(); // 默认使用目标类型的注解,如果没有则使用其实现接口的注解 for (Class<?> clazz : target.getInterfaces()) { resolveDataSource(clazz, signature.getMethod()); } resolveDataSource(target, signature.getMethod()); } /** * 提取目标对象方法注解和类型注解中的数据源标识 * @param clazz * @param method */ private void resolveDataSource(Class<?> clazz, Method method) { try { Class<?>[] types = method.getParameterTypes(); // 默认使用类型注解 if (clazz.isAnnotationPresent(DataSource.class)) { DataSource source = clazz.getAnnotation(DataSource.class); DynamicDataSourceHolder.setDataSource(source.value()); } // 方法注解可以覆盖类型注解 Method m = clazz.getMethod(method.getName(), types); if (m != null && m.isAnnotationPresent(DataSource.class)) { DataSource source = m.getAnnotation(DataSource.class); DynamicDataSourceHolder.setDataSource(source.value()); } } catch (Exception e) { System.out.println(clazz + ":" + e.getMessage()); } } /** * @param * @return void * 方法调用完毕后,清除缓存的数据源,不然在多线程的切换中会报错,导致另一个数据源的数据使用了前一个数据源的数据 */ private void clearDataSource() { System.out.println("=================清除缓存=============="); DynamicDataSourceHolder.clearDataSource(); } }
在spring配置自定义的aop代理:记住一定要设置在事务前面!这里启动cligb代理和动态代理都可以。
这里我将其优先级设置为0, order="0"
<!--基于aspectj注解的配置文件在spring中的配置--> <aop:aspectj-autoproxy expose-proxy="true"/> <!--对这里加以改动,直接在类中进行配置--> <!--设置切面进行拦截--> <aop:config> <!--设置事务的优先级--> <aop:aspect ref="dataSwitchAop" order="0"> <!--拦截所有的service的方法--> <!--当然也可以拦截Controller方法,这里设置的是拦截service--> <aop:pointcut id="dataSourcePointcut" expression="execution(* com.ilongsay.service.*Service..*(..))"/> <aop:before method="intercept" pointcut-ref="dataSourcePointcut"/> <aop:after method="clearDataSource" pointcut-ref="dataSourcePointcut"/> </aop:aspect> </aop:config> <!--如果要设置拦截controller方法的话需要将参数设置为true,启动cligb代理--> <aop:aspectj-autoproxy proxy-target-class="true"/>
这样下来,一切都OK了,就等着去搞事情了,多数据源弄懂了这个,整合100个数据源也不是难事啊!
这是我本人在项目搭建的时候遇到的坑,再总结下吧:
1、sqlite需要配置成为绝对路径
2、需要设置:clearDataSource()这个方法
3、aop的优先级必须设置在事务的前面
自学不易,还请各位复制粘贴党标记一下我的文章路径,也算是对我的一种鼓励。
效果:我这里偷懒用了sout,也可以使用logger日志来代替