ホームページ  >  記事  >  Java  >  JavaのSpring Hibernateでテーブル名を動的に置き換える方法

JavaのSpring Hibernateでテーブル名を動的に置き換える方法

黄舟
黄舟オリジナル
2017-08-09 10:05:142298ブラウズ

1. 概要

実際、最も簡単な方法は、session.createSQLQuery("sql") などのネイティブ SQL を使用するか、jdbcTemplate を使用することです。ただし、プロジェクトでは HQL クエリが使用されており、変更するのは面倒で危険です。したがって、うまくいかない場合は、より良い解決策を見つける必要があります。 3 日間の研究の結果、最終的に良い方法を見つけました。それを以下に説明します。

2. ステップ

2.1 新しい Hibernate インターセプター クラスを作成します

/**
 * Created by hdwang on 2017/8/7.
 *
 * hibernate拦截器:表名替换
 */
public class AutoTableNameInterceptor extends EmptyInterceptor {

    private String srcName = StringUtils.EMPTY; //源表名
    private String destName = StringUtils.EMPTY; // 目标表名

    public AutoTableNameInterceptor() {}

    public AutoTableNameInterceptor(String srcName,String destName){
        this.srcName = srcName;
        this.destName = destName;
    }


    @Override
    public String onPrepareStatement(String sql) {
        if(srcName.equals(StringUtils.EMPTY) || destName.equals(StringUtils.EMPTY)){
            return  sql;
        }
        sql = sql.replaceAll(srcName, destName);
        return sql;
    }
}

このインターセプターは、すべてのデータベース操作をインターセプトし、SQL ステートメント内のテーブル名を送信前に置き換えます。

2.2 sessionFactory への設定

まず sessionFactory とは何かを見てみましょう。


<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean" >
        <property name="dataSource" ref="defaultDataSource"></property>
        <property name="packagesToScan">
            <list>
                <value>com.my.pay.task.entity</value>
                <value>com.my.pay.paycms.entity</value>
                <value>com.my.pay.data.entity.payincome</value>
            </list>
        </property>
        <property name="mappingLocations">
             <list>
                 <value>classpath*:/hibernate/hibernate-sql.xml</value>
             </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
                <prop key="hibernate.show_sql">false</prop>
                <prop key="hibernate.format_sql">false</prop>
                <prop key="hibernate.hbm2ddl.auto">none</prop>
                <!-- 开启查询缓存 -->
                <prop key="hibernate.cache.use_query_cache">false</prop>
                <!-- 配置二级缓存 --> 
                <prop key="hibernate.cache.use_second_level_cache">true</prop>
                <!-- 强制Hibernate以更人性化的格式将数据存入二级缓存 -->  
                  <prop key="hibernate.cache.use_structured_entries">true</prop> 
                  <!-- Hibernate将收集有助于性能调节的统计数据 -->  
                  <prop key="hibernate.generate_statistics">false</prop>
                  <!-- 指定缓存配置文件位置 -->
                  <prop key="hibernate.cache.provider_configuration_file_resource_path">/spring/ehcache.xml</prop>
                <prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
                <prop key="hibernate.current_session_context_class">jta</prop>
                <prop key="hibernate.transaction.factory_class">org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory</prop>
                <prop key="hibernate.transaction.manager_lookup_class">com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup</prop>
            </props>
        </property>
    </bean>
public class LocalSessionFactoryBean extends HibernateExceptionTranslator
        implements FactoryBean<SessionFactory>, ResourceLoaderAware, InitializingBean, DisposableBean {

    private DataSource dataSource;

    private Resource[] configLocations;

    private String[] mappingResources;

    private Resource[] mappingLocations;

    private Resource[] cacheableMappingLocations;

    private Resource[] mappingJarLocations;

    private Resource[] mappingDirectoryLocations;

    private Interceptor entityInterceptor;

    private NamingStrategy namingStrategy;

    private Object jtaTransactionManager;

    private Object multiTenantConnectionProvider;

    private Object currentTenantIdentifierResolver;

    private RegionFactory cacheRegionFactory;

    private Properties hibernateProperties;

    private Class<?>[] annotatedClasses;

    private String[] annotatedPackages;

    private String[] packagesToScan;

    private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

    private Configuration configuration;

    private SessionFactory sessionFactory;

実際、sessionFactory は LocalSessionFactoryBean オブジェクトの属性です。これは、Bean インジェクションがクラス自体ではなくクラスの属性である理由です。 FactoryBeana7bd7c0154d420bde695d24770c97980 インターフェイスを実装します。 sessionFacotry は、LocalSessionFactoryBean オブジェクトを構成することによって生成されます。生成後、sessionFactory オブジェクトが Spring コンテナーに挿入されます。デフォルトでは、これが唯一のシングルトンです。
私たちは皆、sessionFactory オブジェクトによって生成されるセッション オブジェクトを使用してデータベースを操作します。以下は、sessionFactory オブジェクトの 2 つのメソッドです:

    /**
     * Open a {@link Session}.
     * <p/>
     * JDBC {@link Connection connection(s} will be obtained from the
     * configured {@link org.hibernate.service.jdbc.connections.spi.ConnectionProvider} as needed
     * to perform requested work.
     *
     * @return The created session.
     *
     * @throws HibernateException Indicates a problem opening the session; pretty rare here.     */
    public Session openSession() throws HibernateException;    /**
     * Obtains the current session.  The definition of what exactly "current"
     * means controlled by the {@link org.hibernate.context.spi.CurrentSessionContext} impl configured
     * for use.
     * <p/>
     * Note that for backwards compatibility, if a {@link org.hibernate.context.spi.CurrentSessionContext}
     * is not configured but JTA is configured this will default to the {@link org.hibernate.context.internal.JTASessionContext}
     * impl.
     *
     * @return The current session.
     *
     * @throws HibernateException Indicates an issue locating a suitable current session.     */
    public Session getCurrentSession() throws HibernateException;

次に、プロジェクトは getCurrentSession() を使用してセッション オブジェクトを取得します。

休止状態インターセプターを構成するにはどうすればよいですか?

LocalSessionFactoryBean对象的entityInterceptor属性可以配置,你可以在xml中配置它,加到sessionFactory这个bean的xml配置中去。


<property name="entityInterceptor">
      <bean class="com.my.pay.common.AutoTableNameInterceptor"/>
      </property>

まあ、設定できるのは 1 つだけです。 sessionFactory はシングルトンであるため、sessionFactory を参照する Dao オブジェクトもシングルトンにすることができ、サービスとコントローラーはすべてシングルトンになります。そこで質問があります。テーブル名を動的に置き換えるにはどうすればよいでしょうか?動的な複数のケースへの道はブロックされています。残っているのは、インターセプター オブジェクトの値を動的に変更することだけです。良いアドバイスのように聞こえます。私の試みは失敗に終わり、スレッドの安全性の問題を解決できませんでした。その理由については後述する。

そのため、XML で構成しても私のニーズを実現できません。幸いなことに、sessionFactory オブジェクトはそれを変更するためのエントリ ポイントを提供します。


@Resource(name = "sessionFactory")private SessionFactory sessionFactory;protected Session getSession(){        
if(autoTableNameInterceptorThreadLocal.get() == null){            
return this.sessionFactory.getCurrentSession();
        }else{
            SessionBuilder builder = this.sessionFactory.withOptions().interceptor(autoTableNameInterceptorThreadLocal.get());
            Session session = builder.openSession();            
            return session;
        }
}
/**
* 线程域变量,高效实现线程安全(一个请求对应一个thread)
*/
private ThreadLocal<AutoTableNameInterceptor> autoTableNameInterceptorThreadLocal = new ThreadLocal<>();

public List<WfPayLog> find(Long merchantId, Long poolId,String sdk, Long appId,String province,
            Integer price,
            String serverOrder, String imsi,Integer iscallback,String state,
            Date start, Date end, Paging paging) {
       。。。。

        //定制表名拦截器,设置到线程域
        autoTableNameInterceptorThreadLocal.set(new AutoTableNameInterceptor("wf_pay_log","wf_pay_log_"+ DateUtil.formatDate(start,DateUtil.YEARMONTH_PATTERN)));
        List<WfPayLog> wfPayLogs;
        if (paging == null) {
            wfPayLogs = (List<WfPayLog>) find(hql.toString(), params); //find方法里面有 this.getSession().createQuery("hql") 等方法
    } else { 
       wfPayLogs = (List<WfPayLog>) findPaging(hql.toString(), "select count(*) " + hql.toString(), params, paging); 
    }
       return wfPayLogs; 
}

赤でマークされたコードはコアコードとコアの説明です。これは、DAO層オブジェクトにおいて、sessionFactoryオブジェクトを注入してセッションを作成することでデータベースを操作できるようにするという意味で、セッションの取得方法を変更しました。テーブル名を変更する必要がある場合は、スレッド ドメイン変数を定義し、インターセプターが必要なときにインターセプター オブジェクトをスレッド ドメインに保存します。これにより、データベースを操作するためにインターセプターで構成されたセッションが取得されます。インターセプターが有効になる時間。

スレッド ドメイン変数を使用せずにオブジェクト メンバー変数を直接定義することは間違いなく不可能です。同時実行性の問題 (複数のリクエスト (スレッド) が dao メソッドを同時に呼び出し、dao メソッドの実行時に getSession() メソッドが呼び出される) が発生するためです。メソッドが実行されると、おそらく getSession のときにインターセプターが他のリクエストに置き換えられます)、もちろん synchronized で解決することもできます。スレッド ドメインの使用は、同期された同期ロックよりもはるかに効率的です。スレッド ドメインを使用すると、実行ステートメントが同じスレッド内にある限り、インターセプタ オブジェクトとリクエスト (スレッド) が確実に結び付けられ、スレッドによって共有されるオブジェクト情報が一貫している必要があります。したがって、同時実行の問題はありません。

上で述べたように、シングルトン インターセプターはスレッド セーフティの問題を解決できないため機能しません。 AutoTableNameInterceptor はシングルトンです。新しい set オペレーションを追加するなど、dao レイヤーでその値を変更することは問題ありません。ただし、設定中に他のリクエストも設定されるため、リクエストがシリアル (1 つずつキューに入れられる) でない限り、destName と srcName の値が変化し続けることになります。そして、おそらく n dao インスタンスがインターセプターを呼び出すでしょう。スレッドの同期はどのように達成するのでしょうか? dao の操作中にインターセプター オブジェクト全体をロックしないと、パフォーマンスに影響します。 これは、スレッド ドメインを使用して実現することはできません。テストの結果、リクエスト スレッドの代わりにインターセプター メソッドを呼び出す複数のスレッドが休止状態の下部に存在することがわかりました。したがって、dao からインターセプターまではスレッドではなくなります。インターセプターの onPrepareStatement コールバック メソッドは非常に単調で、機能が限られています。さらに、シングルトンの使用は sessionFactory のグローバル構成であり、コードを通じて追加するのは一時的なものです。

以上がJavaのSpring Hibernateでテーブル名を動的に置き換える方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。