spring hibernate实现动态替换表名(分表)的方法

作者:jingxian 时间:2022-06-10 14:20:03 

1.概述

其实最简单的办法就是使用原生sql,如 session.createSQLQuery("sql"),或者使用jdbcTemplate。但是项目中已经使用了hql的方式查询,修改起来又累,风险又大!所以,必须找到一种比较好的解决方案,实在不行再改写吧!经过3天的时间的研究,终于找到一种不错的方法,下面讲述之。

2.步骤

2.1 新建hibernate interceptor类


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

这个interceptor会拦截所有数据库操作,在发送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对象的一个属性,这点可以在LocalSessionFactoryBean类中可以看到,至于bean的注入为何是class的属性而非class本身,那是因为它实现了 FactoryBean<SessionFactory> 接口。sessionFacotry是由LocalSessionFactoryBean对象配置后生成的。生成后将sessionFactory对象注入到了spring容器,且仅此一个而已,默认单例嘛。

我们对数据库的操作都是用session对象,它是由sessionFactory对象生成的。下面是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;

那我们的项目使用getCurrentSession()获取session对象的。

hibernate interceptor怎么配置呢?

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


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

那,它只能配置一个。因为sessionFactory是单例,他也只能是单例,引用sessionFactory的Dao对像也是单例,service,controller通通都是单例。那么有个问题就是,动态替换表名,如何动态?动态多例这条路已经封死了。那只剩下,动态修改interceptor对象的值。听起来像是不错的建议。我尝试后只能以失败告终,无法解决线程安全问题!待会儿描述原因。

所以配置到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对象创建session就可以操作数据库了,我们改变了session的获取方式。当需要改变表名的时候,我们定义线程域变量,在需要interceptor的时候将interceptor对象保存到线程域中去,然后你操作的时候再拿到这个配置有 * 的session去操作数据库,这个时候interceptor就生效了。

不用线程域变量保存,直接定义对象成员变量肯定是不行的,因为会有并发问题(多个请求(线程)同时调用dao方法,dao方法执行的时候又调用getSession()方法,可能当你getSession的时候,别的请求,已经把interceptor给换掉了。),当然用synchronized也可以解决。线程域的使用,比synchronized同步锁高效得多。线程域的使用,保证了interceptor对象和请求(线程)是绑在一起的,dao方法的执行,只要执行语句在同一个线程内,线程所共享的对象信息肯定一致的,所以不存在并发问题。

上面曾说过,单例interceptor不行,原因是:无法解决线程安全问题。 AutoTableNameInterceptor是一个单例,你在dao层可以修改他的值,比如新增set操作,没问题。可是你set的同时,别的请求也在set,就会导致destName,srcName的值一直在变动,除非你的请求是串行的(排队的,一个一个来的)。而且可能n个dao实例都会调用interceptor, 你怎么实现线程同步?除非你在dao操作的时候锁住整个interceptor对象,这个多影响性能! 使用线程域,没法实现,经过测试,发现hibernate底层会有多个线程调用interceptor方法,而不是我们的请求线程!所以,从dao到interceptor已经不是一个线程。interceptor的onPrepareStatement回调方法又是如此的单调,功能有限,哎。再说了,使用单例,是sessionFactory的全局配置,影响效率,通过代码添加是临时性的。

标签:spring,hibernate,动态,替换表名
0
投稿

猜你喜欢

  • Mybatis-Plus注入SQL原理分析

    2022-11-09 21:17:22
  • Spring Boot 2.X 快速集成单元测试解析

    2023-11-11 02:45:05
  • Android TextView中文字通过SpannableString设置属性用法示例

    2023-07-26 07:11:51
  • Swagger实现动态条件注入与全局拦截功能详细流程

    2023-11-23 13:41:05
  • java input 调用手机相机和本地照片上传图片到服务器然后压缩的方法

    2023-04-26 08:20:27
  • 使用JavaWeb webSocket实现简易的点对点聊天功能实例代码

    2023-10-29 00:14:17
  • Java中的break和continue关键字的使用方法总结

    2022-07-13 11:50:46
  • Java全面细致讲解Wrapper的使用

    2023-02-14 00:49:24
  • Java Main 函数启动不退出的解决方案

    2022-03-24 14:25:03
  • shiro实现单点登录(一个用户同一时刻只能在一个地方登录)

    2022-07-04 01:37:55
  • Android 6.0动态权限申请教程

    2023-09-26 16:43:56
  • Spring Boot 定义系统启动任务的多种方式

    2023-11-24 13:25:33
  • Java 守护线程_动力节点Java学院整理

    2023-11-28 07:51:14
  • 基于idea Maven中的redis配置使用详解

    2023-11-29 11:57:28
  • Java8中关于Function.identity()的使用

    2021-11-16 16:05:15
  • JDBC实现学生管理系统

    2023-01-22 13:22:19
  • 被kafka-client和springkafka版本坑到自闭及解决

    2023-08-23 15:07:36
  • Java代码实现酒店管理系统

    2023-08-13 13:09:23
  • Mybatis-plus实现主键自增和自动注入时间的示例代码

    2022-11-05 00:22:51
  • Java对xls文件进行读写操作示例代码

    2023-08-04 17:55:31
  • asp之家 软件编程 m.aspxhome.com