Home  >  Article  >  Java  >  How to dynamically replace table names in spring hibernate in Java

How to dynamically replace table names in spring hibernate in Java

黄舟
黄舟Original
2017-08-09 10:05:142299browse

1. Overview

In fact, the simplest way is to use native sql, such as session.createSQLQuery("sql"), or use jdbcTemplate. However, hql query has been used in the project, which is tiring to modify and risky! Therefore, we must find a better solution. If it doesn’t work, rewrite it! After 3 days of research, I finally found a good method, which is described below.

2. Steps

2.1 Create a new hibernate interceptor class

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

This interceptor will intercept all database operations and replace them before sending the sql statement. table name.

2.2 Configure to sessionFactory

Let’s first take a look at what sessionFactory is.


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

In fact, sessionFactory is an attribute of the LocalSessionFactoryBean object. This can be seen in the LocalSessionFactoryBean class. As for the injection of the bean, why is it injected? It is an attribute of the class rather than the class itself, because it implements the FactoryBeana7bd7c0154d420bde695d24770c97980 interface. sessionFacotry is generated by configuring the LocalSessionFactoryBean object. After generation, the sessionFactory object is injected into the spring container, and this is the only one. It is a singleton by default.
We all operate the database using the session object, which is generated by the sessionFactory object. The following are two methods of the sessionFactory object:

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

Then our project uses getCurrentSession() to obtain the session object.

How to configure hibernate interceptor?

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


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

Then, it can only configure one. Because sessionFactory is a singleton, it can only be a singleton. The Dao object that references sessionFactory is also a singleton. Service and controller are all singletons. So there is a question: how to dynamically replace the table name? The road to dynamic multiple cases has been blocked. All that's left is to dynamically modify the value of the interceptor object. Sounds like good advice. My attempts only ended in failure and could not solve the thread safety problem! The reasons will be described later.

So configuring it in xml cannot achieve my needs. Then it can only be set in code. Fortunately, the sessionFactory object provides us with an entry point to modify it.


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

The code marked in red is the core code and core description. This means that in the DAO layer object, the database can be operated by injecting the sessionFactory object to create a session. We have changed the method of obtaining the session. When we need to change the table name, we define thread domain variables and save the interceptor object to the thread domain when an interceptor is needed. Then when you operate, you will get the session configured with the interceptor to operate the database. At this time The interceptor takes effect.

Without saving thread domain variables, it is definitely not possible to directly define object member variables, because there will be concurrency issues (multiple requests (threads) call the dao method at the same time, and the getSession() method is called when the dao method is executed) , maybe when you getSession, the interceptor has been replaced by other requests), of course it can also be solved with synchronized. The use of thread domains is much more efficient than synchronized synchronization locks. The use of the thread domain ensures that the interceptor object and the request (thread) are tied together. When executing the dao method, as long as the execution statement is in the same thread, the object information shared by the threads must be consistent, so there is no concurrency problem.

As mentioned above, the singleton interceptor does not work because it cannot solve the thread safety problem. AutoTableNameInterceptor is a singleton. You can modify its value at the dao layer, such as adding a new set operation, no problem. But while you are setting, other requests are also being set, which will cause the values ​​​​of destName and srcName to keep changing, unless your requests are serial (queued, one by one). And maybe n dao instances will call the interceptor. How do you achieve thread synchronization? Unless you lock the entire interceptor object during dao operation, this will affect performance! This cannot be achieved using the thread domain. After testing, we found that there will be multiple threads calling the interceptor method at the bottom of hibernate, not our request thread! Therefore, from dao to interceptor is no longer a thread. The interceptor's onPrepareStatement callback method is so monotonous and has limited functions, hey. Besides, using a singleton is a global configuration of the sessionFactory, which affects efficiency. Adding it through code is temporary.

The above is the detailed content of How to dynamically replace table names in spring hibernate in Java. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn