当前位置:主页 > 软件编程 > JAVA代码 >

spring注解 @PropertySource配置数据源全流程

时间:2023-01-13 11:03:13 | 栏目:JAVA代码 | 点击:

@PropertySource数据源配置

一般在配置数据源是都会使用xml的方式注入,key-value在properties中管理;spring4.X已有着比较完善的注解来替换xml的配置方式。

使用xml配置数据源

通常我们使用xml配置数据源,使用SpEL获取properties中的配置。

applicationContext.xml 中配置 dataSource 及 PreferencesPlaceholderConfigurer,使用 PropertyPlaceholderConfigurer进行Bean属性替换

<bean id="configProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
        <property name="locations">
            <list>
                <value>classpath:/jdbc.properties</value>
            </list>
        </property>
        <property name="fileEncoding" value="utf-8"/>
    </bean>
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
    <property name="properties" ref="configProperties" />
</bean>
<!-- 使用proxool连接池的数据源, -->
<bean id="dataSource" class="org.logicalcobwebs.proxool.ProxoolDataSource">
    <!-- 数据源的别名 -->
    <property name="alias" value="${proxool.alias}" /> 
    <!-- 驱动 -->
    <property name="driver" value="${proxool.driver}" /> 
    <!-- 链接URL  -->
    <property name="driverUrl" value="${proxool.driverUrl}" /> 
    <!-- 用户名-->
    <property name="user" value="${proxool.user}" />
    <!-- 密码 -->
    <property name="password" value="${proxool.password}" /> 
    <!-- 最大链接数-->
    <property name="maximumConnectionCount" value="${proxool.maximumConnectionCount}" /> 
    <!-- 最小链接数 -->
    <property name="minimumConnectionCount" value="${proxool.minimumConnectionCount}" /> 
    <!-- ...(略) -->
</bean> 

jdbc.properties

proxool.alias=mySql
proxool.driver=com.mysql.jdbc.Driver
proxool.driverUrl=jdbc:mysql://localhost:3306/test?characterEncoding=utf8
proxool.user=root
proxool.password=root
proxool.maximumActiveTime=1200
proxool.maximumConnectionCount=50
#...

使用javaBean配置数据源

DataSourceConfiguration类是数据源的javaBean配置方式,@Configuratio注解当前类,

spring启动时会扫描被@Configuratio注解的类,注入当前类中配置的方法bean;

当然别忘了启用注解扫描:

<context:annotation-config/>  
<context:component-scan base-package="com.XXX.test.dateSource"></context:component-scan>

@value注解读取配置

@value中可以直接使用SpEL,获取properties配置,成员变量也不需要getter、setter,不过还是有一个前提,需要配置xml:

<bean id="configProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
        <property name="locations">
            <list>
                <value>classpath:/jdbc.properties</value>
            </list>
        </property>
        <property name="fileEncoding" value="utf-8"/>
    </bean>
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
    <property name="properties" ref="configProperties" />
</bean>

@Bean注解:spring扫面当前类时,注入每个有@Bean注解的方法的返回值Bean, name属性默认为返回值类类名首字母小写,这里自己设置name。

package com.XXX.test.dateSource;
import org.logicalcobwebs.proxool.ProxoolDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuratio
public class DataSourceConfiguration{
    @Value("${proxool.alias}")
    private String alias;
    @Value("${proxool.driver}")
    private String driver;
    @Value("${proxool.driverUrl}")
    private String driverUrl;
    @Value("${proxool.user}")
    private String user;
    @Value("${proxool.password}")
    private String password;
    //...
    @Bean(name="dataSource")
    public ProxoolDataSource dataSource(){
         ProxoolDataSource proxoolDataSource = new ProxoolDataSource();
         proxoolDataSource.setDriver(driver);
         proxoolDataSource.setDriverUrl(driverUrl);
         proxoolDataSource.setUser(user);
         proxoolDataSource.setPassword(password);
         //...
         return proxoolDataSource;
     }
 }

这时dataSource已被注入,使用时可注解注入,如下:

    @Autowired
    private ProxoolDataSource dataSource;

@PropertySource注解读取配置

@PropertySource注解当前类,参数为对应的配置文件路径,这种方式加载配置文件,可不用在xml中配置PropertiesFactoryBean引入jdbc.properties,使用时方便得多,DataSourceConfiguration不再需要成员变量,取而代之的是需要注入一个Environment环境配置,使用env.getProperty(key)获取数据:

package com.XXX.test.dateSource;
import org.logicalcobwebs.proxool.ProxoolDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
@Configuratio
@PropertySource("classpath:/jdbc.properties")
public class DataSourceConfiguration{
    @Autowired
    private Environment env;
    @Bean(name="dataSource")
    public ProxoolDataSource dataSource(){
         ProxoolDataSource proxoolDataSource = new ProxoolDataSource();
         proxoolDataSource.setDriver(env.getProperty("proxool.alias"));
         proxoolDataSource.setDriverUrl(env.getProperty("proxool.driver"));
         proxoolDataSource.setUser(env.getProperty("proxool.user"));
         proxoolDataSource.setPassword(env.getProperty("proxool.password"));
         //...
         return proxoolDataSource;
     }
 }

这里主要是说明注解的用法,所以没有具体体现数据源全部参数的配置。对于有强迫症的来说若项目中所有bean都使用注解,几乎不太希望仅dataSource用xml类配置,换成类的方式类配置强迫感就消失了! 

注解的spring多数据源配置及使用

前一段时间研究了一下spring多数据源的配置和使用,为了后期从多个数据源拉取数据定时进行数据分析和报表统计做准备。由于之前做过的项目都是单数据源的,没有遇到这种场景,所以也一直没有去了解过如何配置多数据源。

后来发现其实基于spring来配置和使用多数据源还是比较简单的,因为spring框架已经预留了这样的接口可以方便数据源的切换。

先看一下spring获取数据源的源码

可以看到AbstractRoutingDataSource获取数据源之前会先调用determineCurrentLookupKey方法查找当前的lookupKey,这个lookupKey就是数据源标识。

因此通过重写这个查找数据源标识的方法就可以让spring切换到指定的数据源了。

第一步:创建一个DynamicDataSource的类

继承AbstractRoutingDataSource并重写determineCurrentLookupKey方法,代码如下:

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        // 从自定义的位置获取数据源标识
        return DynamicDataSourceHolder.getDataSource();
    }
}

第二步:创建DynamicDataSourceHolder

用于持有当前线程中使用的数据源标识,代码如下:

public class DynamicDataSourceHolder {
    /**
     * 注意:数据源标识保存在线程变量中,避免多线程操作数据源时互相干扰
     */
    private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<String>();
    public static String getDataSource() {
        return THREAD_DATA_SOURCE.get();
    }
    public static void setDataSource(String dataSource) {
        THREAD_DATA_SOURCE.set(dataSource);
    }
    public static void clearDataSource() {
        THREAD_DATA_SOURCE.remove();
    }
}

第三步:配置多个数据源

和第一步里创建的DynamicDataSource的bean,简化的配置如下:

<!--创建数据源1,连接数据库db1 -->
<bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${db1.driver}" />
    <property name="url" value="${db1.url}" />
    <property name="username" value="${db1.username}" />
    <property name="password" value="${db1.password}" />
</bean>
<!--创建数据源2,连接数据库db2 -->
<bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${db2.driver}" />
    <property name="url" value="${db2.url}" />
    <property name="username" value="${db2.username}" />
    <property name="password" value="${db2.password}" />
</bean>
<!--创建数据源3,连接数据库db3 -->
<bean id="dataSource3" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${db3.driver}" />
    <property name="url" value="${db3.url}" />
    <property name="username" value="${db3.username}" />
    <property name="password" value="${db3.password}" />
</bean>
<bean id="dynamicDataSource" class="com.test.context.datasource.DynamicDataSource">
    <property name="targetDataSources">
        <map key-type="java.lang.String">
            <!-- 指定lookupKey和与之对应的数据源 -->
            <entry key="dataSource1" value-ref="dataSource1"></entry>
            <entry key="dataSource2" value-ref="dataSource2"></entry>
            <entry key="dataSource3 " value-ref="dataSource3"></entry>
        </map>
    </property>
    <!-- 这里可以指定默认的数据源 -->
    <property name="defaultTargetDataSource" ref="dataSource1" />
</bean>

到这里已经可以使用多数据源了,在操作数据库之前只要DynamicDataSourceHolder.setDataSource("dataSource2")即可切换到数据源2并对数据库db2进行操作了。

示例代码如下:

@Service
public class DataServiceImpl implements DataService {
    @Autowired
    private DataMapper dataMapper;
    @Override
    public List<Map<String, Object>> getList1() {
        // 没有指定,则默认使用数据源1
        return dataMapper.getList1();
    }
    @Override
    public List<Map<String, Object>> getList2() {
        // 指定切换到数据源2
        DynamicDataSourceHolder.setDataSource("dataSource2");
        return dataMapper.getList2();
    }
    @Override
    public List<Map<String, Object>> getList3() {
        // 指定切换到数据源3
        DynamicDataSourceHolder.setDataSource("dataSource3");
        return dataMapper.getList3();
    }
}

----------------------------华丽的分割线----------------------------

但是问题来了,如果每次切换数据源时都调用DynamicDataSourceHolder.setDataSource("xxx")就显得十分繁琐了,而且代码量大了很容易会遗漏,后期维护起来也比较麻烦。能不能直接通过注解的方式指定需要访问的数据源呢,比如在dao层使用@DataSource("xxx")就指定访问数据源xxx?当然可以!前提是,再加一点额外的配置^_^。

首先,我们得定义一个名为DataSource的注解,代码如下:

@Target({ TYPE, METHOD })
@Retention(RUNTIME)
public @interface DataSource {
    String value();
}

然后,定义AOP切面以便拦截所有带有注解@DataSource的方法,取出注解的值作为数据源标识放到DynamicDataSourceHolder的线程变量中:

public class DataSourceAspect {
    /**
     * 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源
     *
     * @param point
     * @throws Exception
     */
    public void intercept(JoinPoint point) throws Exception {
        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());
        }
    }
}

最后在spring配置文件中配置拦截规则就可以了,比如拦截service层或者dao层的所有方法:

<bean id="dataSourceAspect" class="com.test.context.datasource.DataSourceAspect" />
    <aop:config>
        <aop:aspect ref="dataSourceAspect">
            <!-- 拦截所有service方法 -->
            <aop:pointcut id="dataSourcePointcut" expression="execution(* com.test.*.dao.*.*(..))"/>
            <aop:before pointcut-ref="dataSourcePointcut" method="intercept" />
        </aop:aspect>
    </aop:config>
</bean>

OK,这样就可以直接在类或者方法上使用注解@DataSource来指定数据源,不需要每次都手动设置了。

示例代码如下:

@Service
// 默认DataServiceImpl下的所有方法均访问数据源1
@DataSource("dataSource1")
public class DataServiceImpl implements DataService {
    @Autowired
    private DataMapper dataMapper;
    @Override
    public List<Map<String, Object>> getList1() {
        // 不指定,则默认使用数据源1
        return dataMapper.getList1();
    }
    @Override
    // 覆盖类上指定的,使用数据源2
    @DataSource("dataSource2")
    public List<Map<String, Object>> getList2() {
        return dataMapper.getList2();
    }
    @Override
    // 覆盖类上指定的,使用数据源3
    @DataSource("dataSource3")
    public List<Map<String, Object>> getList3() {
        return dataMapper.getList3();
    }
}

提示:注解@DataSource既可以加在方法上,也可以加在接口或者接口的实现类上,优先级别:方法>实现类>接口。也就是说如果接口、接口实现类以及方法上分别加了@DataSource注解来指定数据源,则优先以方法上指定的为准。

您可能感兴趣的文章:

相关文章