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的全局配置,影响效率,通过代码添加是临时性的。代码添加仅仅是添加到这个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实现动态替换表名(分表)的更多相关文章
- spring boot:shardingsphere多数据源,支持未分表的数据源(shardingjdbc 4.1.1)
一,为什么要给shardingsphere配置多数据源? 1,shardingjdbc默认接管了所有的数据源, 如果我们有多个非分表的库时,则最多只能设置一个为默认数据库, 其他的非分表数据库不能访问 ...
- SQL查询数据库信息, 数据库表名, 数据库表信息
SQL查询数据库信息, 数据库表名, 数据库表信息 ---------------------------------------------- -- 以下例子, 在sql_server 中可以直接运 ...
- oracle看到用户的所有表名、表睐、字段名称、现场的目光、是空的、字段类型
--oracle看到用户的所有表名.表睐.字段名称.现场的目光.是空的.字段类型 select distinct TABLE_COLUMN.*, TABLE_NALLABLE.DATA_TYPE, T ...
- oracle 中查询当前用户可以看到的表名、表对应的所有字段 原
转自:https://my.oschina.net/u/3783799/blog/2870207 1.oracle 查询当前用户下的表名,表注释 select t.table_name, f.comm ...
- mysql如何查询多样同样的表/sql分表查询、java项目日志表分表的开发思路/按月分表
之前开发的一个监控系统,数据库的日志表是单表,虽然现在数据还不大并且做了查询sql优化,不过以后数据库的日志表数据肯定会越来越庞大,将会导致查询缓慢,所以把日志表改成分表,日志表可以按时间做水平分表, ...
- struts+spring+hibernate两张表字段名一样处理方法
在利用struts2+spring+hibernate(利用Hibernate进行分页查询)三大框架进行开发项目的时候,出现一个问题:居然要进行关联查询的十几张表中有两张表的字段一样,并且这两张表中的 ...
- Spring Boot中整合Sharding-JDBC单库分表示例
本文是Sharding-JDBC采用Spring Boot Starter方式配置第二篇,第一篇是读写分离讲解,请参考:<Spring Boot中整合Sharding-JDBC读写分离示例> ...
- 表单验证:$tablePrefix(定义表前缀);$trueTableName = 'yonghu',找到真实表名(yonghu)表;create($attr,0)两个参数;批量验证(返回数组);ajax+动态验证表单
*$tablePrefix是定义在Model中的,优先级大于配置文件中,如果项目中表前缀全部比如为"a_",并且在配置文件中定义了 'DB_PREFIX'=>'a_' 后期如 ...
- Hibernate 根据实体名称得到DB表名以及表对应的Sequence name
DB: oracle 10g; entityName:com.signaldemand.flank.hibernate.model.实体名 1. 根据实体名获取DB表相对应的表名 Class<? ...
随机推荐
- MS SQL Server字符拆分函数
Insus.NET以前有用XQuery的nodes()方法写过一个函数,是MS SQL字符拆分的函数,http://www.cnblogs.com/insus/archive/2012/02/26/2 ...
- Centos 定时任务发送smtp邮件
接着上一篇文章...... 1.首先创建一个sheel的脚本命令,我是在home文件夹下面创建的命令: touch a.sh 2.编辑a.sh脚本 vim a.sh ,键入键盘 i 键 准备插入 ...
- Kubernetes学习之路(二十三)之资源指标和集群监控
目录 1.资源指标和资源监控 2.Weave Scope监控集群 (1)Weave Scope部署 (2)使用 Scope (3)拓扑结构 (4)实时资源监控 (5)在线操作 (6)强大的搜索功能 2 ...
- HTML基础之CSS
CSS选择器 1.id选择器 2.class选择器 3.标签选择器 4.层级选择器(空格) 5.组合选择器(逗号) 6.属性选择器(中括号) <!DOCTYPE html> <htm ...
- Spring Boot (十四): Spring Boot 整合 Shiro-登录认证和权限管理
这篇文章我们来学习如何使用 Spring Boot 集成 Apache Shiro .安全应该是互联网公司的一道生命线,几乎任何的公司都会涉及到这方面的需求.在 Java 领域一般有 Spring S ...
- R绘图 第九篇:绘制散点图和气泡图(ggplot2)
绘制散点图(scatterplots)使用geom_point()函数,气泡图(bubblechart)也是一个散点图,只不过点的大小由一个变量(size)来控制.散点图潜在的最大问题是过度绘图:当一 ...
- Python对Selenium调用浏览器进行封装包括启用无头浏览器,及对应的浏览器配置文件
""" 获取浏览器 打开本地浏览器 打开远程浏览器 关闭浏览器 打开网址 最大化 最小化 标题 url 刷新 Python对Selenium封装浏览器调用 ------b ...
- 《杜增强讲Unity之Tanks坦克大战》1-准备工作
0.案例介绍 0.1开始界面 点击Play Now 进入游戏界面 左边的坦克使用ws控制前后移动,ad键左右旋转,空格键开火 右边的坦克使用方向键上下控制前后移动,方向键左右键实现左右旋转 ...
- 关于T/G/M/K
//扫盲贴 K, G, T,都是表数量,只是个数字,在不同的场合下表示的不同.在计算机行业中,这几个量可用来表示数据传输速度和容量,下面分别讨论,希望不了解的朋友不要被某知道上的误解了.如果有什么错误 ...
- 第十五次ScrumMeeting博客
第十五次ScrumMeeting博客 本次会议于12月4日(一)22时整在3公寓725房间召开,持续30分钟. 与会人员:刘畅.辛德泰.张安澜.赵奕.方科栋. 1. 每个人的工作(有Issue的内容和 ...