SSM框架之多数据源整合sqlite与mysql,遇到的坑都在这里了

前言:本人非计算机专业,因为毕业后的迷茫选择了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日志来代替

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值