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的全局配置,影响效率,通过代码添加是临时性的。代码添加仅仅是添加到这个session而已,这点可以从源码看出。下面贴出源码

public interface SessionFactoryImplementor extends Mapping, SessionFactory {

}

public final class SessionFactoryImpl
implements SessionFactoryImplementor { @Override
public SessionBuilder withOptions() {
return new SessionBuilderImpl( this );
} static class SessionBuilderImpl implements SessionBuilder {
private final SessionFactoryImpl sessionFactory;
private Interceptor interceptor;
private Connection connection;
private ConnectionReleaseMode connectionReleaseMode;
private boolean autoClose;
private boolean autoJoinTransactions = true;
private boolean flushBeforeCompletion;
private String tenantIdentifier; SessionBuilderImpl(SessionFactoryImpl sessionFactory) {
this.sessionFactory = sessionFactory;
final Settings settings = sessionFactory.settings; // set up default builder values...
this.interceptor = sessionFactory.getInterceptor();
this.connectionReleaseMode = settings.getConnectionReleaseMode();
this.autoClose = settings.isAutoCloseSessionEnabled();
this.flushBeforeCompletion = settings.isFlushBeforeCompletionEnabled();
} protected TransactionCoordinatorImpl getTransactionCoordinator() {
return null;
} @Override
public Session openSession() {
return new SessionImpl(
connection,
sessionFactory,
getTransactionCoordinator(),
autoJoinTransactions,
sessionFactory.settings.getRegionFactory().nextTimestamp(),
interceptor,
flushBeforeCompletion,
autoClose,
connectionReleaseMode,
tenantIdentifier
);
} @Override
public SessionBuilder interceptor(Interceptor interceptor) {
this.interceptor = interceptor;
return this;
} @Override
public SessionBuilder noInterceptor() {
this.interceptor = EmptyInterceptor.INSTANCE;
return this;
} @Override
public SessionBuilder connection(Connection connection) {
this.connection = connection;
return this;
} @Override
public SessionBuilder connectionReleaseMode(ConnectionReleaseMode connectionReleaseMode) {
this.connectionReleaseMode = connectionReleaseMode;
return this;
} @Override
public SessionBuilder autoJoinTransactions(boolean autoJoinTransactions) {
this.autoJoinTransactions = autoJoinTransactions;
return this;
} @Override
public SessionBuilder autoClose(boolean autoClose) {
this.autoClose = autoClose;
return this;
} @Override
public SessionBuilder flushBeforeCompletion(boolean flushBeforeCompletion) {
this.flushBeforeCompletion = flushBeforeCompletion;
return this;
} @Override
public SessionBuilder tenantIdentifier(String tenantIdentifier) {
this.tenantIdentifier = tenantIdentifier;
return this;
}
} }
代码中给出了从sessionFactory->openSession的过程,sessionFacotry->withOptions->sessionBuilder->openSession->session,new SessionImpl构造出了session对象,内部也没有针对sessionFactory的修改(代码没粘贴),所以withOptions的核心功能是,利用已有的sessionFacotry构造出特定的session。

3.经过多翻测试,还发现一个问题

spring对http请求的处理,采用的是线程池,并不是每个请求单独重新创建一个线程。即请求与线程的关系是多对一,不是一对一。这样就带来一个问题,因为ThreadLocal的绑定对象是线程Thread,因为线程池的关系,同一个线程绑定的数据,在不同的请求中都可以获取到。

因为项目中,对表名的替换有采用hql的,也用了sql的,且同时出现在同一个类中。就是说同一个Dao对象中的两个方法,一个使用hql,一个使用sql查询,分别对应session.createQuery 和 session.createSQLQuery。可惜hibernate interceptor是对session的所有操作都拦截。因为我们对普通的sql查询,采用的是直接修改表名的方式,并不想采用hibernate interceptor策略去修改。故而,导致普通的查询方式,表名被替换了两次,一次自己的主动修改,一次interceptor。这肯定不行,解决方法如下:

移除interceptor,我上面是通过threadLocal的值判断是否添加interceptor的,所以移除threadLocal即可。在find方法return前,remove掉。

autoTableNameInterceptorThreadLocal.remove();

这样,即使在同一个类中,同一个threadLocal,不同查询方式,因为调用不同的session,而做到互不干扰。核心关键就是我们针对ThreadLocal这个全局变量值的设定操作完后及时移除了。

原来一直以为,每个请求会新建线程去处理的,妈的,又被坑了一次。线程池真是个坑货。所以所,ThreadLocal虽然解决了并发问题,不一定真正解决了你的问题,你的问题还可能是线程内问题!像这个就是线程内问题。多个请求,多次请求均可能被此线程处理,全局变量的使用,实在是危险至极!

4.spring+hibernate版本

<properties>
<hibernate.version>4.1.0.Final</hibernate.version>
<spring.version>4.0.0.RELEASE</spring.version>
</properties>
 

5.参考文章

http://blog.csdn.net/meng2602956882/article/details/22914493

https://my.oschina.net/cloudcross/blog/831277

http://liuguxing.iteye.com/blog/889448

http://blog.csdn.net/qq_24489717/article/details/70147100

http://redhat.iteye.com/blog/1057974

http://ks2144634.blog.163.com/blog/static/13358550320109895135535/

http://blog.csdn.net/unifirst/article/details/50482031

spring hibernate实现动态替换表名(分表)的更多相关文章

  1. spring boot:shardingsphere多数据源,支持未分表的数据源(shardingjdbc 4.1.1)

    一,为什么要给shardingsphere配置多数据源? 1,shardingjdbc默认接管了所有的数据源, 如果我们有多个非分表的库时,则最多只能设置一个为默认数据库, 其他的非分表数据库不能访问 ...

  2. SQL查询数据库信息, 数据库表名, 数据库表信息

    SQL查询数据库信息, 数据库表名, 数据库表信息 ---------------------------------------------- -- 以下例子, 在sql_server 中可以直接运 ...

  3. oracle看到用户的所有表名、表睐、字段名称、现场的目光、是空的、字段类型

    --oracle看到用户的所有表名.表睐.字段名称.现场的目光.是空的.字段类型 select distinct TABLE_COLUMN.*, TABLE_NALLABLE.DATA_TYPE, T ...

  4. oracle 中查询当前用户可以看到的表名、表对应的所有字段 原

    转自:https://my.oschina.net/u/3783799/blog/2870207 1.oracle 查询当前用户下的表名,表注释 select t.table_name, f.comm ...

  5. mysql如何查询多样同样的表/sql分表查询、java项目日志表分表的开发思路/按月分表

    之前开发的一个监控系统,数据库的日志表是单表,虽然现在数据还不大并且做了查询sql优化,不过以后数据库的日志表数据肯定会越来越庞大,将会导致查询缓慢,所以把日志表改成分表,日志表可以按时间做水平分表, ...

  6. struts+spring+hibernate两张表字段名一样处理方法

    在利用struts2+spring+hibernate(利用Hibernate进行分页查询)三大框架进行开发项目的时候,出现一个问题:居然要进行关联查询的十几张表中有两张表的字段一样,并且这两张表中的 ...

  7. Spring Boot中整合Sharding-JDBC单库分表示例

    本文是Sharding-JDBC采用Spring Boot Starter方式配置第二篇,第一篇是读写分离讲解,请参考:<Spring Boot中整合Sharding-JDBC读写分离示例> ...

  8. 表单验证:$tablePrefix(定义表前缀);$trueTableName = 'yonghu',找到真实表名(yonghu)表;create($attr,0)两个参数;批量验证(返回数组);ajax+动态验证表单

    *$tablePrefix是定义在Model中的,优先级大于配置文件中,如果项目中表前缀全部比如为"a_",并且在配置文件中定义了 'DB_PREFIX'=>'a_' 后期如 ...

  9. Hibernate 根据实体名称得到DB表名以及表对应的Sequence name

    DB: oracle 10g; entityName:com.signaldemand.flank.hibernate.model.实体名 1. 根据实体名获取DB表相对应的表名 Class<? ...

随机推荐

  1. Maltego——互联网情报聚合工具初探(转)

    有时候你可曾想过,从一个Email,或者Twitter,或是网站,甚至姓名等等,能找到一个人千丝万缕的联系,并把这些联系整合,利用起来?Maltego就是这样一款优秀而强大的工具.Maltego允许从 ...

  2. c# 无边框窗体的边框阴影

    Windows API: using System; using System.Collections.Generic; using System.ComponentModel; using Syst ...

  3. 20155306 白皎 免考实践总结——0day漏洞

    本次免考实践提纲及链接 第一部分 基础知识 1.1 0day漏洞概述 1.2二进制文件概述 1.3 必备工具 1.4 crack实验 第二部分 漏洞利用 2.1栈溢出利用 2.1.1 系统栈工作原理 ...

  4. python 回溯法 子集树模板 系列 —— 17、找零问题

    问题 有面额10元.5元.2元.1元的硬币,数量分别为3个.5个.7个.12个.现在需要给顾客找零16元,要求硬币的个数最少,应该如何找零?或者指出该问题无解. 分析 元素--状态空间分析大法:四种面 ...

  5. 行级安全(Row-Level Security)

    通过授予和拒绝(Grant/Deny)命令控制用户的权限,只能控制用户对数据库对象的访问权限,这意味着,用户访问的粒度是对象整体,可以是一个数据表,或视图等,用户要么能够访问数据库对象,要么没有权限访 ...

  6. Partition4:增加分区

    在关系型 DB中,分区表经常使用DateKey(int 数据类型)作为Partition Column,每个月的数据填充到同一个Partition中,由于在Fore-End呈现的报表大多数是基于Mon ...

  7. Markdown 编辑器

    桌面编辑器 MarkdownPad Pro 版注册邮箱: Soar360@live.com 授权密钥: GBPduHjWfJU1mZqcPM3BikjYKF6xKhlKIys3i1MU2eJHqWGI ...

  8. Azure 基础:Queue Storage

    Azure Storage 是微软 Azure 云提供的云端存储解决方案,当前支持的存储类型有 Blob.Queue.File 和 Table. 笔者在前文中介绍了 File Storage 的基本用 ...

  9. 如何在Windows Server 2003搭建Windows+iis+asp+access环境

    前提系统盘镜像要加载进来方案一:开始->管理您的服务器->添加或删除角色->下一步->自定义配置->下一步->选择应用程序服务器(IIS,ASP.NET)-> ...

  10. mac10.12.6系统使用cmake安装opencv3.3.0+opencv_contrib-3.3.0

    brew与cmake brew安装 /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/ins ...