Spring Framework で複数のデータ ソースを作成、ロード、動的に切り替えるための構成サンプル コード

高洛峰
リリース: 2017-02-08 10:44:25
オリジナル
1791 人が閲覧しました

私たちのプロジェクトではこのような問題が発生しました。私たちのプロジェクトは複数のデータベースに接続する必要があり、異なる顧客は訪問するたびにニーズに応じて異なるデータベースにアクセスします。以前は、常に Spring および Hibernate フレームワークでデータ ソースを構成していたため、sessionFactory の dataSource 属性は常にこのデータ ソースを指し、sessionFactory を使用する場合、すべての DAO はこのデータ ソースを通じてデータベースにアクセスします。しかし現在、プロジェクトのニーズにより、DAO は sessionFactory にアクセスするときに複数のデータ ソースを常に切り替える必要があります。データの永続化を実行するときに、顧客のニーズに応じて sessionFactory を動的に切り替える方法が異なります。データソース? Spring フレームワークでいくつかの変更を加えれば解決できるでしょうか?活用できるデザインパターンはありますか?

問題の分析

私は最初に、Spring の applicationContext ですべての dataSource を構成することを考えました。これらの dataSource は、Oracle、SQL Server、MySQL などのさまざまなデータベースなどのさまざまなタイプである場合もあれば、Apache によって提供される org.apache.commons.dbcp.BasicDataSource や Spring によって提供される org などの異なるデータ ソースである場合もあります。 . springframework.jndi.JndiObjectFactoryBean など次に、sessionFactory は、データ ソースを切り替える目的を達成するために、各顧客の要求に従って dataSource 属性を異なるデータ ソースに設定します。

しかし、すぐに問題が見つかりました。複数のユーザーが同時にデータベースにアクセスすると、リソースの競合が発生します。これはすべて「単一ケースパターン」によって引き起こされます。ご存知のとおり、Spring フレームワークを使用する場合、beanFactory に登録された Bean は基本的にシングルトン モードを採用します。つまり、Spring が開始されると、これらの Bean はメモリにロードされ、各 Bean はプロジェクト全体にのみ存在します。物体。オブジェクトは 1 つだけであるため、オブジェクトのすべてのプロパティ、より正確にはインスタンス変数は静的変数のように動作します (実際、「静的」と「単一ケース」は非常によく似たものであることが多く、「単一ケース」を実装するには「静的」を使用することがよくあります) ")。私たちの問題を例に挙げると、sessionFactory にはプロジェクト全体でオブジェクトが 1 つだけあり、そのインスタンス変数 dataSource には静的変数と同様にインスタンス変数が 1 つだけあります。異なるユーザーが常に dataSource の値を変更すると、複数のユーザーが変数を求めて競合するという問題が必然的に発生し、システムに隠れた危険が生じます。

上記の分析を通じて、マルチデータ ソース アクセスの問題を解決する鍵は、sessionFactory がデータの永続化を実行するときに、特定のコードを使用して顧客のニーズに応じてデータ ソースを動的に切り替え、リソースの競合を解決できることです。問題。

問題の解決策

Decorator デザイン パターンを使用する

この問題を解決するには、私の考えはこの dataSource にロックされています。 sessionFactoryが指すdataSourceが、顧客のニーズに応じて、顧客が必要とする実際のデータソースに接続できる、つまりデータソースを動的に切り替える機能を提供できれば問題は解決します。どうしようか?使用したい dataSource ソース コードを変更する必要がありますか?これは明らかに良い解決策ではありません。変更を元の dataSource コードから分離する必要があります。上記の分析に基づいて、GoF デザイン パターンで Decorator パターン (デコレータ パターン) を使用することが、選択できる最良のオプションとなるはずです。

「デコレーターモード」とは何ですか?簡単に言うと、元の関数を変更する必要があるが、元のコードを直接変更したくない場合、元のコードをカバーする Decorator を設計します。 Decorator を使用すると、元のクラスとまったく同じになりますが、Decorator の一部の機能は変更が必要な機能に変更されています。 Decorator パターンの構造は図のとおりです。

Spring Framework で複数のデータ ソースを作成、ロード、動的に切り替えるための構成サンプル コード

当初、図内のすべての特定の Component クラスのいくつかの関数を変更する必要がありましたが、コードを直接変更する代わりに、その外側に Decorator を追加しました。 Decorator と特定の Component クラスは両方とも AbstractComponent を継承するため、特定の Component クラスと同じように見えます。つまり、Decorator を使用すると、ConcreteComponentA または ConcreteComponentB を使用するクライアント プログラムも含めて、ConcreteComponentA または ConcreteComponentB を使用するのと同じになります。使用するクラスが Decorator に変更されたことはわかりませんが、Decorator は特定の Component クラスのいくつかのメソッドを変更しており、これらのメソッドの実行結果は異なります。

MultiDataSource クラスを設計します

さて、問題に戻ります。dataSource の関数を変更する必要がありますが、dataSource 内のコードは変更したくありません。ここで言う dataSource は、javax.sql.DataSource インターフェースを実装するすべてのクラスで、よく使われるクラスには、Apache が提供する org.apache.commons.dbcp.BasicDataSource や、Spring が提供する org.springframework.jndi.JndiObjectFactoryBean などが含まれます。これらのクラスを使用せず、動的にデータ ソースを割り当てる機能を実現するためにクラスを 1 つずつ変更することはもちろん、dataSource を使用する sessionFactory がそのような変更を感じないようにすることもできます。全て。 Decorator パターンは、この問題を解決するデザイン パターンです。

まず、MultiDataSource という名前の Decorator クラスを作成し、それを使用してデータ ソースを動的に切り替えます。同時に、構成ファイルで sessionFactory の dataSource 属性を特定の dataSource から MultiDataSource に変更します。図に示すように:

Spring Framework で複数のデータ ソースを作成、ロード、動的に切り替えるための構成サンプル コード

元の Decorator モードと比較すると、AbstractComponent は抽象クラスですが、ここではこの抽象クラスをインターフェイス、つまり DataSource インターフェイスに置き換えることができ、ConcreteComponent はその実装クラスです。 BasicDataSource、JndiObjectFactoryBean などの DataSource は待機します。 MultiDataSource は、特定の dataSource をカプセル化し、データ ソースの動的な切り替えを実装します。

Java コード

public class MultiDataSource implements DataSource {     
    private DataSource dataSource = null;     
public MultiDataSource(DataSource dataSource){     
        this.dataSource = dataSource;     
    }     
    /* (non-Javadoc)   
     * @see javax.sql.DataSource#getConnection()   
     */    
    public Connection getConnection() throws SQLException {     
        return getDataSource().getConnection();     
    }     
    //其它DataSource接口应当实现的方法     
    
    public DataSource getDataSource(){     
        return this.dataSource;     
        }     
    }     
    public void setDataSource(DataSource dataSource) {     
        this.dataSource = dataSource;     
    }     
}
ログイン後にコピー

顧客がリクエストを行うとき、リクエストに dataSourceName を入れてから、 newMultiDataSource(dataSource を呼び出してリクエストにデータ ソース名を入れます) ) MultiDataSource に顧客が必要とするデータ ソースを伝え、データ ソースを動的に切り替えることができます。しかし、注意深い人は、これがシングルトンの場合に問題になることに気づくでしょう。MultiDataSource にはシステム内にオブジェクトが 1 つしかなく、そのインスタンス変数 dataSource も静的変数と同様に 1 つしかないからです。このため、シングルトン パターンにより多くの設計パターンの変更が余儀なくされました。これについては、私の記事「シングルトンによる設計パターンの変更」で詳しく説明します。では、シングルトン モードではどのように設計すればよいのでしょうか?

シングルトン モードの MultiDataSource

シングルトン モードでは、MultiDataSource のメソッドを呼び出すたびに dataSource が異なる可能性があるため、dataSource をインスタンス変数 dataSource に入れることができない簡単な方法は、メソッドにパラメーターを追加することです。 getDataSource() は、どの dataSource を呼び出しているかを MultiDataSource に伝えます:

java コード

public DataSource getDataSource(String dataSourceName){     
        log.debug("dataSourceName:"+dataSourceName);     
        try{     
            if(dataSourceName==null||dataSourceName.equals("")){     
                return this.dataSource;     
            }     
            return (DataSource)this.applicationContext.getBean(dataSourceName);     
        }catch(NoSuchBeanDefinitionException ex){     
            throw new DaoException("There is not the dataSource   
        }     
    }
ログイン後にコピー

必要なデータ ソースが Spring 構成ファイルに登録されていることは言及する価値があります。 dataSourceName はそれに対応する ID です。

xml コード

<bean id="dataSource1"    
    class="org.apache.commons.dbcp.BasicDataSource">    
    <property name="driverClassName">    
        <value>oracle.jdbc.driver.OracleDrivervalue>    
    property>   
    ......     
bean>    
<bean id="dataSource2"    
    class="org.apache.commons.dbcp.BasicDataSource">    
    <property name="driverClassName">    
        <value>oracle.jdbc.driver.OracleDrivervalue>   
    property>     
    ......     
bean>
ログイン後にコピー

Spring の ApplicationContext を取得するには、MultiDataSource クラスはインターフェース org.springframework.context.ApplicationContextAware を実装し、メソッドを実装する必要があります:

java コード

private ApplicationContext applicationContext = null;     
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {     
        this.applicationContext = applicationContext;     
    }
ログイン後にコピー

このようにして、これを渡すことができます。 applicationContext.getBean( dataSourceName) は dataSource を取得します。

スレッド経由で dataSourceName を渡す

上記の設計を見ると、ユーザーがリクエストを行うときに、どのデータベースに接続する必要があるかという理由で、MultiDataSource はまだ実行できません。データ ソース名がリクエストに含まれています。つまり、データ ソース名を MultiDataSource に渡すには、BUS と DAO のすべてのメソッドで dataSourceName パラメーターを追加する必要があります。それは私たちが見たくないものです。 BUS と DAO をスキップし、スレッドを通じて MultiDataSource に直接渡すクラスを作成するのは良い設計です:

Spring Framework で複数のデータ ソースを作成、ロード、動的に切り替えるための構成サンプル コード

java コード

public class SpObserver {     
    private static ThreadLocal local = new ThreadLocal();     
    public static void putSp(String sp) {     
        local.set(sp);     
    }     
    public static String getSp() {     
        return (String)local.get();     
    }     
}
ログイン後にコピー

フィルターを作成し、顧客がリクエストを行うたびに SpObserver.petSp( )、リクエスト内の dataSourceName を SpObserver オブジェクトに渡します。最後に、MultiDataSource メソッド getDataSource() を変更します。

java コード

public DataSource getDataSource(){     
        String sp = SpObserver.getSp();     
        return getDataSource(sp);     
    }
ログイン後にコピー

完全な MultiDataSource コードは添付ファイルにあります。

データソースを動的に追加する

通过以上方案,我们解决了动态分配数据源的问题,但你可能提出疑问:方案中的数据源都是配置在spring的ApplicationContext 中,如果我在程序运行过程中动态添加数据源怎么办?这确实是一个问题,而且在我们的项目中也确实遇到。spring的 ApplicationContext是在项目启动的时候加载的。加载以后,我们如何动态地加载新的bean到ApplicationContext中 呢?我想到如果用spring自己的方法解决这个问题就好了。所幸的是,在查看spring的源代码后,我找到了这样的代码,编写了 DynamicLoadBean类,只要调用loadBean()方法,就可以将某个或某几个配置文件中的bean加载到 ApplicationContext中(见附件)。不通过配置文件直接加载对象,在spring的源码中也有,感兴趣的朋友可以自己研究。

在spring中配置

在完成了所有这些设计以后,我最后再唠叨一句。我们应当在spring中做如下配置:

xml 代码

<bean id="dynamicLoadBean" class="com.htxx.service.dao.DynamicLoadBean">bean>    
<bean id="dataSource" class="com.htxx.service.dao.MultiDataSource">    
        <property name="dataSource">    
            <ref bean="dataSource1" />    
        property>    
    bean>    
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">    
        <property name="dataSource">    
            <ref bean="dataSource" />    
        property>    
        ......     
    bean>
ログイン後にコピー

其中dataSource属性实际上更准确地说应当是defaultDataSource,即spring启动时以及在客户没有指定数据源时应当指定的默认数据源。

该方案的优势以上方案与其它方案相比,它有哪些优势呢?

 

首先,这个方案完全是在spring的框架下解决的,数据源依然配置在spring的配置文件中,sessionFactory依然去配置它的 dataSource属性,它甚至都不知道dataSource的改变。唯一不同的是在真正的dataSource与sessionFactory之间增 加了一个MultiDataSource。

其次,实现简单,易于维护。这个方案虽然我说了这么多东西,其实都是分析,真正需要我们写的代码就只有MultiDataSource、 SpObserver两个类。MultiDataSource类真正要写的只有getDataSource()和getDataSource(sp)两个 方法,而SpObserver类更简单了。实现越简单,出错的可能就越小,维护性就越高。 

最后,这个方案可以使单数据源与多数据源兼容。这个方案完全不影响BUS和DAO的编写。如果我们的项目在开始之初是单数据源的情况下开发,随着项 目的进行,需要变更为多数据源,则只需要修改spring配置,并少量修改MVC层以便在请求中写入需要的数据源名,变更就完成了。如果我们的项目希望改 回单数据源,则只需要简单修改配置文件。这样,为我们的项目将增加更多的弹性。

特别说明:实例中的DynamicLoadBean在web环境下运行会出错,需要将类中 AbstractApplicationContext改为 org.springframework.context.ConfigurableApplicationContext。

更多Spring Framework で複数のデータ ソースを作成、ロード、動的に切り替えるための構成サンプル コード相关文章请关注PHP中文网!

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!