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

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)

相关推荐

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

    1.概述 其实最简单的办法就是使用原生sql,如 session.createSQLQuery("sql"),或者使用jdbcTemplate.但是项目中已经使用了hql的方式查询,修改起来又累,风险又大!所以,必须找到一种比较好的解决方案,实在不行再改写吧!经过3天的时间的研究,终于找到一种不错的方法,下面讲述之. 2.步骤 2.1 新建hibernate interceptor类 /** * Created by hdwang on 2017/8/7. * * hibernate拦

  • mysql表名忽略大小写配置方法详解

    linux下mysql默认是要区分表名大小写的.mysql是否区分大小写设置是由参数lower_case_table_names决定的,其中: 1)lower_case_table_names = 0  区分大小写(即对大小写不敏感),默认是这种设置.这样设置后,在mysql里创建的表名带不带大写字母都没有影响,都可以正常读出和被引用. 2)lower_case_table_names = 1  不区分大小写(即对大小写敏感).这样设置后,表名在硬盘上以小写保存,MySQL将所有表名转换为小写存

  • Spring Boot 集成 Sharding-JDBC + Mybatis-Plus 实现分库分表功能

    一. Sharding-jdbc简介 " Sharding-jdbc是开源的数据库操作中间件:定位为轻量级Java框架,在Java的JDBC层提供的额外服务.它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架. 官方文档地址:https://shardingsphere.apache.org/document/current/cn/overview/ 本文demo实现了分库分表功能.如有错误,欢迎各位在评论中指出.不

  • springboot jpa分库分表项目实现过程详解

    这篇文章主要介绍了springboot jpa分库分表项目实现过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 分库分表场景 关系型数据库本身比较容易成为系统瓶颈,单机存储容量.连接数.处理能力都有限.当单表的数据量达到1000W或100G以后,由于查询维度较多,即使添加从库.优化索引,做很多操作时性能仍下降严重.此时就要考虑对其进行切分了,切分的目的就在于减少数据库的负担,缩短查询时间. 分库分表用于应对当前互联网常见的两个场景--大数

  • SpringBoot 如何使用sharding jdbc进行分库分表

    目录 基于4.0版本,Springboot2.1 在pom里确保有如下引用 里面我profiles.active了另一个 之后手工把表都建好 写个测试代码 需要注意一个坑 基于4.0版本,Springboot2.1 之前写过一篇使用sharding-jdbc进行分库分表的文章,不过当时的版本还比较早,现在已经不能用了.这一篇是基于最新版来写的. 新版已经变成了shardingsphere了,https://shardingsphere.apache.org/. 有点不同的是,这一篇,我们是采用多

  • SpringBoot 2.0 整合sharding-jdbc中间件实现数据分库分表

    一.水平分割 1.水平分库 1).概念:  以字段为依据,按照一定策略,将一个库中的数据拆分到多个库中. 2).结果  每个库的结构都一样:数据都不一样:  所有库的并集是全量数据: 2.水平分表 1).概念  以字段为依据,按照一定策略,将一个表中的数据拆分到多个表中. 2).结果  每个表的结构都一样:数据都不一样:  所有表的并集是全量数据: 二.Shard-jdbc 中间件 1.架构图 2.特点 1).Sharding-JDBC直接封装JDBC API,旧代码迁移成本几乎为零. 2).适

  • Mybatis-plus使用TableNameHandler分表详解(附完整示例源码)

    为什么要分表 Mysql是当前互联网系统中使用非常广泛的关系数据库,具有ACID的特性. 但是mysql的单表性能会受到表中数据量的限制,主要原因是B+树索引过大导致查询时索引无法全部加载到内存.读取磁盘的次数变多,而磁盘的每次读取对性能都有很大的影响. 这时一个简单可行的方案就是分表(当然土豪也可以堆硬件),将一张数据量庞大的表的数据,拆分到多个表中,这同时也减少了B+树索引的大小,减少磁盘读取次数,提高性能. 两种基础分表逻辑 说完了为什么要分表,下面聊聊业务开发中常见的两种基础的分表逻辑.

  • 超大数据量存储常用数据库分表分库算法总结

    当一个应用的数据量大的时候,我们用单表和单库来存储会严重影响操作速度,如mysql的myisam存储,我们经过测试,200w以下的时候,mysql的访问速度都很快,但是如果超过200w以上的数据,他的访问速度会急剧下降,影响到我们webapp的访问速度,而且数据量太大的话,如果用单表存储,就会使得系统相当的不稳定,mysql服务很容易挂掉.所以当数据量超过200w的时候,建议系统工程师还是考虑分表. 以下是几种常见的分表算法. 1.按自然时间来分表/分库; 如一个应用的数据在一年后数据量会达到2

  • 什么是分表和分区 MySql数据库分区和分表方法

    1.为什么要分表和分区 日常开发中我们经常会遇到大表的情况,所谓的大表是指存储了百万级乃至千万级条记录的表.这样的表过于庞大,导致数据库在查询和插入的时候耗时太长,性能低下,如果涉及联合查询的情况,性能会更加糟糕.分表和表分区的目的就是减少数据库的负担,提高数据库的效率,通常点来讲就是提高表的增删改查效率. 2.什么是分表和分区 2.1 分表 分表是将一个大表按照一定的规则分解成多张具有独立存储空间的实体表,我们可以称为子表,每个表都对应三个文件,MYD数据文件,.MYI索引文件,.frm表结构

  • PHP操作mysql数据库分表的方法

    一般来说,当我们的数据库的数据超过了100w记录的时候就应该考虑分表或者分区了,这次我来详细说说分表的一些方法.首先,我们需要想好到底分多少个 表,前提当然是满足应用.这里我使用了一个比较简单的分表方法,就是根据自增id的尾数来分,也就是说分0-9一共10个表,其取值也很好做,就是对10 进行取模.另外,还可以根据某一字段的md5值取其中几位进行分表,这样的话,可以分的表就很多了. 好了,先来创建表吧,代码如下: CREATE TABLE `ttlsa_com`.`article_0` ( `i

随机推荐