Maison  >  Article  >  Java  >  Comment remplacer dynamiquement les noms de table lors de la mise en veille prolongée du printemps en Java

Comment remplacer dynamiquement les noms de table lors de la mise en veille prolongée du printemps en Java

黄舟
黄舟original
2017-08-09 10:05:142299parcourir

1. Présentation

En fait, le moyen le plus simple est d'utiliser du SQL natif, tel que session.createSQLQuery("sql"), ou d'utiliser jdbcTemplate. Cependant, une requête hql a été utilisée dans le projet, ce qui est fatiguant à modifier et risqué ! Par conséquent, nous devons trouver une meilleure solution. Si cela ne fonctionne pas, réécrivez-la ! Après 3 jours de recherche, j'ai enfin trouvé une bonne méthode, décrite ci-dessous.

2. Étapes

2.1 Créer une nouvelle classe d'intercepteur d'hibernation

/**
 * 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;
    }
}

Cet intercepteur interceptera toutes les opérations de base de données et le remplacera avant d'envoyer le instruction sql. Supprimez le nom de la table.

2.2 Configurer sessionFactory

Jetons d'abord un coup d'œil à ce qu'est 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;

En fait, sessionFactory est un attribut de l'objet LocalSessionFactoryBean. Cela peut être vu dans la classe LocalSessionFactoryBean. pourquoi l'injection de bean est une propriété de la classe plutôt que de la classe elle-même, c'est parce qu'elle implémente l'interface FactoryBeana7bd7c0154d420bde695d24770c97980 sessionFacotry est généré en configurant l'objet LocalSessionFactoryBean. Après génération, l'objet sessionFactory est injecté dans le conteneur Spring, et c'est le seul. C'est un singleton par défaut.
Nous exploitons tous la base de données en utilisant l'objet session, qui est généré par l'objet sessionFactory. Voici deux méthodes de l'objet sessionFactory :

    /**
     * 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;

Ensuite, notre projet utilise getCurrentSession() pour obtenir l'objet session.

Comment configurer l'intercepteur de mise en veille prolongée ?

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


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

Ensuite, il ne peut en configurer qu'un seul. Parce que sessionFactory est un singleton, il ne peut s'agir que d'un singleton. L'objet Dao qui fait référence à sessionFactory est également un singleton. Le service et le contrôleur sont tous des singletons. Une question se pose donc : comment remplacer dynamiquement le nom de la table ? La voie vers des cas multiples dynamiques a été bloquée. Il ne reste plus qu'à modifier dynamiquement la valeur de l'objet intercepteur. Cela semble être un bon conseil. Mes tentatives se sont soldées par un échec et n'ont pas pu résoudre le problème de sécurité des threads ! Les raisons seront décrites plus tard.

Donc, le configurer en XML ne peut pas répondre à mes besoins. Ensuite, il ne peut être défini que dans le code. Heureusement, l'objet sessionFactory nous fournit une entrée pour le modifier.


@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; 
}

Le code marqué en rouge est le code principal et la description principale. Cela signifie que dans l'objet de couche DAO, la base de données peut être exploitée en injectant l'objet sessionFactory pour créer une session. Nous avons modifié la méthode d'obtention de la session. Lorsque nous devons changer le nom de la table, nous définissons les variables du domaine du thread et enregistrons l'objet intercepteur dans le domaine du thread lorsqu'un intercepteur est nécessaire. Ensuite, lorsque vous opérerez, vous obtiendrez la session configurée avec l'intercepteur pour faire fonctionner la base de données. heure L'intercepteur prend effet.

Il n'est certainement pas possible de définir directement des variables membres d'objet sans variables de domaine de thread, car il y aura des problèmes de concurrence (plusieurs requêtes (threads) appellent la méthode dao en même temps, et la méthode getSession() est appelé lorsque la méthode dao est exécutée), peut-être que lorsque vous getSession, l'intercepteur a été remplacé par d'autres requêtes), bien sûr, cela peut également être résolu avec synchronisé. L'utilisation de domaines de threads est bien plus efficace que les verrous de synchronisation synchronisés. L'utilisation du domaine thread garantit que l'objet intercepteur et la requête (thread) sont liés ensemble lors de l'exécution de la méthode dao, tant que l'instruction d'exécution est dans le même thread, les informations sur l'objet partagées par les threads doivent être cohérentes. il n'y a donc pas de problème de concurrence.

Comme mentionné ci-dessus, l'intercepteur singleton ne fonctionne pas car il ne peut pas résoudre le problème de sécurité des threads. AutoTableNameInterceptor est un singleton. Vous pouvez modifier sa valeur au niveau de la couche dao, par exemple en ajoutant une nouvelle opération d'ensemble, sans problème. Mais pendant que vous effectuez la configuration, d'autres requêtes sont également définies, ce qui entraînera la modification continue des valeurs de destName et srcName, à moins que vos requêtes ne soient en série (mises en file d'attente, une par une). Et peut-être que n instances dao appelleront l'intercepteur. Comment réaliser la synchronisation des threads ? À moins que vous ne verrouilliez l'intégralité de l'objet intercepteur pendant l'opération dao, cela affectera les performances ! Cela ne peut pas être réalisé en utilisant le domaine de thread.Après les tests, nous avons constaté qu'il y aurait plusieurs threads appelant la méthode intercepteur au bas de la mise en veille prolongée, pas notre thread de requête ! Par conséquent, du dao à l’intercepteur n’est plus un fil. La méthode de rappel onPrepareStatement de l'intercepteur est tellement monotone et a des fonctions limitées, hé. De plus, l'utilisation d'un singleton est une configuration globale de sessionFactory, ce qui affecte l'efficacité. Son ajout via le code est temporaire.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn