[From] http://www.07net01.com/2016/01/1172051.html
最近在做OA系统(ssh),一直在想如何把框架架得更完善,此前已经在框架里集成springMVC,读写分离(这个在另一篇文章里会有说明怎么做),这几天在想如果是大数据,要分表要怎么来弄,不可能每一个表都写一个实体来映射,这样太不灵活,也不现实!
oa考勤本来数据不多,如果分表一般以年份来分表。今天研究的是以用户来分表。
网上找了些资料,说是可以用NamingStrategy来实现动态映射表名。
发现sessionFactory里面有个namingStrategy属性可以注入,这个东东就是hibernate在表映射时要用的,关系啥的都放在这个里面,只要重写它就行。不过只有在容器启动时才会加载这个,也就是只会执行一次,以后就算动态设置了namingStrategy也不会再加载改变映射关系了,那会就是一个sessionFactory只能在启动时对应一个namingStrategy;如此要想对应多个相同表映射就须得要有多个sessionFactory才行;
可是spring并没有提供像AbstractRoutingDataSource动态数据源这样的接口给我们用,那怎么办,只能自己写一个了,既然我们要多个sessionFactory,但我们的底层dao里是只注入一个sessionFactory,那么我们就模仿动态数据源一样写个路由DynamicSessionFactory继承sessionFactory,并且提供一个动态getHibernateSessionFactory的方法,然后写个实现DynamicSessionFactoryImpl重写sessionFactory里面的方法,让里面的方法在取sessionFactory时全部通过getHibernateSessionFactory得到,而getHibernateSessionFactory就是动态根据我们的设置来取sessionFactory的了,这样就实现动态取不同的sessionFactory了。
getHibernateSessionFactory靠ThreadLocal的特性,给当前线程设置sessionFactory的bean名称,然后在取时就拿这个bean名称取对应的bean就行了。
在配好这些后,当时想的是用spring的aop来拦截service层的方法来进行动态设置bean名称,在实践后发现这样做在单表或者说是在一次请求只调一个service方法时且不懒加载时才可以,当一个action请求有两个以上service方法时,就完了,每调一个方法sessionFactory就会重新设置一下,这样session就一定会关闭了,这样在调完方法查出数据后如果去懒加载get数据就会发现没有session,就报错了(collection is not associated with any session)。所以我们不能在service层做文章,得在action层甚至是filter里做动态设置才行。
发现在web.xml里有个org.springframework.orm.hibernate4.support.OpenSessionInViewFilter,这个的用处就是为了解决一个请求里session统一的问题的,不过这里虽然配置了,但一在service层动态设置sessionFactory后,每次都给改变了。所以我们只要在这个filter里来动态设置sessionFactory就行了,这样每次一个请求就设置一个sessionFactory,直到完成。实现:就是写一个MyOpenSessionInViewFilter去继承OpenSessionInViewFilter然后重写它的lookupSessionFactory方法,在每次请求lookupSessionFactory动态设上自己要的sessionFactory就行了。
这样设置完成后测试发现可以达到效果,可点着点着就发现问题了,一到使用多线程(在生成多人的排班考勤时我都是使用多线程来生成的)时,就会报当前线程的session为空。仔细想想也不难理解,每次设置时我都是只设置了请求的这条线程的话,也就是只有主线程是有了的长时间session存在的,当用多线程时,每一条新线程都在拿sessionFactory时都会调ThreadLocal当前线程的存的sessionFactory的名称来取,这样当然就取不到了,想着就把主线程的sessionFactory的名称也传进子线程设置在子线程这样就有了吧,结果发现是有了,sessionFactory取对了,可是在取当前session时又报错取不到了。这又是怎么回事呢,于是又想着把主线程那个session也传进去试试,发现传进去,哎,不报错了,是取得到了,我去,却发现它不在事务里了。至此得整理下思路,一个线程一个session的话,那多线程中的子线程就不能再用那个session了,得是它自己启动一个session事务,可想着那些service方法本来我就是有设置事务的呀!然后去看看事务的配置文件,哎哟喂,事务配的是org.springframework.orm.hibernate4.HibernateTransactionManager,里面配的是路由sessionFactory,原来是事务没有动态呀,这样每次调用时都是同一个,没有动态给多个sessionFactory都设置事务。于是写一个动态事务DynamicTransactionManager去继承HibernateTransactionManager,并重写getSessionFactory和getDataSource方法,这样就实现了动态事务的动态sessionFactory了。然后再测试下多线程的,搞定!
历时两天多,终于搞定了!
平时没怎么写文章,文字表达能力不好,凑合着看吧,以后得多练练
下面上一下代码吧!
动态表名实现类:
/**
* 动态表名
*/
import <a target=_blank href="http://www.07net01.com/tags-Java-0.html" target="_blank" class="infotextkey" style="box-sizing: border-box; color: rgb(66, 139, 202); background: transparent;">Java</a>.text.DecimalFormat;
import java.util.ArrayList;
import org.hibernate.cfg.DefaultNamingStrategy;
public class TNamingStrategy extends DefaultNamingStrategy {
/**
*
*/
private static final long serial<a target=_blank href="http://www.07net01.com/tags-version-0.html" target="_blank" class="infotextkey" style="box-sizing: border-box; color: rgb(66, 139, 202); background: transparent;">version</a>UID = 1L;
private static final DecimalFormat df = new DecimalFormat();
private static ArrayList<String> rollingTables = new ArrayList<String>();
private String suffix;//表后缀
static {
rollingTables.add("KQ_ATTENDANCE");//有哪些表需要多个映射的就在这里add进去
rollingTables.add("KQ_DUTY_ROSTER");
df.applyPattern("00");
}
/* @Override
public String classToTableName(String className) {
// TODO Auto-generated method stub
return tableName(StringHelper.unqualify(className).toUpperCase());
} */
/**
*
*
* class里显式设置了表名,就调用 tableName
* 没有显示就调用 classToTableName
*/
public String tableName(String tableName) {
String stroeTable = tableName;
// 对指定的表名计算实际<a target=_blank href="http://www.07net01.com/storage_networking/" target="_blank" class="infotextkey" style="box-sizing: border-box; color: rgb(66, 139, 202); background: transparent;">存储</a>表名
if (rollingTables.contains(tableName.toUpperCase())&&suffix!=null) {
stroeTable += "_" + suffix;
<a target=_blank href="http://www.07net01.com/tags-system-0.html" target="_blank" class="infotextkey" style="box-sizing: border-box; color: rgb(66, 139, 202); background: transparent;">system</a>.out.println("store record into [" + stroeTable + "]");
}
return stroeTable;
}
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
}
当前线程保存sessonFactory bean名称:
public abstract class CustomerContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setCustomerType(String customerType) {
contextHolder.set(customerType);
}
public static String getCustomerType() {
if(contextHolder.get()==null)
{
System.out.println("空啦");
setCustomerType("master");
}
return contextHolder.get();
}
public static void clearCustomerType() {
contextHolder.remove();
}
}
路由sessionFactory:
import org.hibernate.SessionFactory;
public <a target=_blank href="http://www.07net01.com/tags-interface-0.html" target="_blank" class="infotextkey" style="box-sizing: border-box; color: rgb(66, 139, 202); background: transparent;">interface</a> DynamicSessionFactory extends SessionFactory {
public SessionFactory getHibernateSessionFactory();
}
路由sessionFactory实现:
import java.io.Serializable;
import java.sql.Connection;
import java.util.Map;
import java.util.Set;
import javax.naming.NamingException;
import javax.naming.Reference;
import org.hibernate.Cache;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionBuilder;
import org.hibernate.SessionFactory;
import org.hibernate.StatelessSession;
import org.hibernate.StatelessSessionBuilder;
import org.hibernate.TypeHelper;
import org.hibernate.engine.spi.FilterDefinition;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.metadata.CollectionMetadata;
import org.hibernate.stat.Statistics;
/**
* <b>function:</b> 动态sessionFactory实现
* @version 1.0
*/
@SuppressWarnings({ "unchecked", "deprecation" })
public class DynamicSessionFactoryImpl implements DynamicSessionFactory {
private static final long serialVersionUID = 5384069312247414885L;
private Map<Object, SessionFactory> targetSessionFactorys;
private SessionFactory defaultTargetSessionFactory;
/**
*
* <b>function:</b> 重写这个方法,这里最关键
*/
@Override
public SessionFactory getHibernateSessionFactory() {
SessionFactory targetSessionFactory = targetSessionFactorys.get(CustomerContextHolder.getCustomerType());
// System.out.println("CustomerContextHolder.getCustomerType():"+CustomerContextHolder.getCustomerType());
// System.out.println("targetSessionFactory:"+targetSessionFactory);
if (targetSessionFactory != null) {
return targetSessionFactory;
} else if (defaultTargetSessionFactory != null) {
return defaultTargetSessionFactory;
}
return null;
}
public SessionFactory getObject()
{
return this.getHibernateSessionFactory();
}
@Override
public void close() throws HibernateException {
this.getHibernateSessionFactory().close();
}
@Override
public boolean containsFetchProfileDefinition(String s) {
return this.getHibernateSessionFactory().containsFetchProfileDefinition(s);
}
@Override
public void evict(Class clazz) throws HibernateException {
this.getHibernateSessionFactory().evict(clazz);
}
@Override
public void evict(Class clazz, Serializable serializable) throws HibernateException {
this.getHibernateSessionFactory().evict(clazz, serializable);
}
@Override
public void evictCollection(String s) throws HibernateException {
this.getHibernateSessionFactory().evictCollection(s);
}
@Override
public void evictCollection(String s, Serializable serializable) throws HibernateException {
this.getHibernateSessionFactory().evictCollection(s, serializable);
}
@Override
public void evictEntity(String entity) throws HibernateException {
this.getHibernateSessionFactory().evictEntity(entity);
}
@Override
public void evictEntity(String entity, Serializable serializable) throws HibernateException {
this.getHibernateSessionFactory().evictEntity(entity, serializable);
}
@Override
public void evictQueries() throws HibernateException {
this.getHibernateSessionFactory().evictQueries();
}
@Override
public void evictQueries(String queries) throws HibernateException {
this.getHibernateSessionFactory().evictQueries(queries);
}
@Override
public Map<String, ClassMetadata> getAllClassMetadata() {
return this.getHibernateSessionFactory().getAllClassMetadata();
}
@Override
public Map getAllCollectionMetadata() {
return this.getHibernateSessionFactory().getAllClassMetadata();
}
@Override
public Cache getCache() {
return this.getHibernateSessionFactory().getCache();
}
@Override
public ClassMetadata getClassMetadata(Class clazz) {
return this.getHibernateSessionFactory().getClassMetadata(clazz);
}
@Override
public ClassMetadata getClassMetadata(String classMetadata) {
return this.getHibernateSessionFactory().getClassMetadata(classMetadata);
}
@Override
public CollectionMetadata getCollectionMetadata(String collectionMetadata) {
return this.getHibernateSessionFactory().getCollectionMetadata(collectionMetadata);
}
@Override
public Session getCurrentSession() throws HibernateException {
return this.getHibernateSessionFactory().getCurrentSession();
}
@Override
public Set getDefinedFilterNames() {
return this.getHibernateSessionFactory().getDefinedFilterNames();
}
@Override
public FilterDefinition getFilterDefinition(String definition) throws HibernateException {
return this.getHibernateSessionFactory().getFilterDefinition(definition);
}
@Override
public Statistics getStatistics() {
return this.getHibernateSessionFactory().getStatistics();
}
@Override
public TypeHelper getTypeHelper() {
return this.getHibernateSessionFactory().getTypeHelper();
}
@Override
public boolean isClosed() {
return this.getHibernateSessionFactory().isClosed();
}
@Override
public Session openSession() throws HibernateException {
return this.getHibernateSessionFactory().openSession();
}
@Override
public StatelessSession openStatelessSession() {
return this.getHibernateSessionFactory().openStatelessSession();
}
@Override
public StatelessSession openStatelessSession(Connection connection) {
return this.getHibernateSessionFactory().openStatelessSession(connection);
}
@Override
public Reference getReference() throws NamingException {
return this.getHibernateSessionFactory().getReference();
}
public void setTargetSessionFactorys(Map<Object, SessionFactory> targetSessionFactorys) {
this.targetSessionFactorys = targetSessionFactorys;
}
public void setDefaultTargetSessionFactory(SessionFactory defaultTargetSessionFactory) {
this.defaultTargetSessionFactory = defaultTargetSessionFactory;
}
@Override
public SessionFactoryOptions getSessionFactoryOptions() {
return this.getHibernateSessionFactory().getSessionFactoryOptions();
}
@Override
public SessionBuilder withOptions() {
return this.getHibernateSessionFactory().withOptions();
}
@Override
public StatelessSessionBuilder withStatelessOptions() {
return this.getHibernateSessionFactory().withStatelessOptions();
}
}
动态的事务管理器:
import javax.sql.DataSource;
import org.hibernate.SessionFactory;
import org.springframework.orm.hibernate4.HibernateTransactionManager;
import org.springframework.orm.hibernate4.SessionFactoryUtils;
/**
* <b>function:</b> 重写HibernateTransactionManager事务管理器,实现自己的动态的事务管理器
* @version 1.0
*/
public class DynamicTransactionManager extends HibernateTransactionManager {
private static final long serialVersionUID = -4655721479296819154L;
/**
* @see org.springframework.orm.hibernate4.HibernateTransactionManager#getDataSource()
* <b>function:</b> 重写
*/
@Override
public DataSource getDataSource() {
return SessionFactoryUtils.getDataSource(getSessionFactory());
}
/**
* @see org.springframework.orm.hibernate4.HibernateTransactionManager#getSessionFactory()
* <b>function:</b> 重写
*/
@Override
public SessionFactory getSessionFactory() {
DynamicSessionFactory dynamicSessionFactory = (DynamicSessionFactory) super.getSessionFactory();
SessionFactory hibernateSessionFactory = dynamicSessionFactory.getHibernateSessionFactory();
return hibernateSessionFactory;
}
}
自己的OpenSessionInViewFilter,为的是一个请求一个session并且有选择的设定sessionFactory:
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.orm.hibernate4.SessionFactoryUtils;
import org.springframework.orm.hibernate4.SessionHolder;
import org.springframework.orm.hibernate4.support.AsyncRequestInterceptor;
import org.springframework.orm.hibernate4.support.OpenSessionInViewFilter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.context.support.WebApplicationContextUtils;
import sy.model.base.frame.SessionInfo;
import sy.model.base.frame.Syuser;
import sy.util.base.ConfigUtil;
/**
* 自己的OpenSessionInViewFilter,为的是一个请求一个session并且有选择的设定sessionFactory
* @author miraclerz
*
*/
public class MyOpenSessionInViewFilter extends OpenSessionInViewFilter {
private HttpServletRequest request;
@Override
protected SessionFactory lookupSessionFactory() {
WebApplicationContext wac = WebApplicationContextUtils
.getRequiredWebApplicationContext(getServletContext());
String sessionFactoryName = getSessionFactoryBeanName();
//动态设置sessionFactory名称了
CustomerContextHolder.setCustomerType("master");
DynamicSessionFactoryImpl dynamicSessionFactoryImpl = wac.getBean(
sessionFactoryName, DynamicSessionFactoryImpl.class);
return dynamicSessionFactoryImpl.getObject();
}
}
上一下配置文件:
每一个sessionFactory就加一个这个配置:
最后在web.xml加上
<filter> <filter-name>openSessionInView</filter-name> <!-- <filter-class>org.springframework.orm.hibernate4.support.OpenSessionInViewFilter</filter-class> --> <filter-class>sy.datasource.sessionFactory.MyOpenSessionInViewFilter</filter-class> <init-param> <param-name>singleSession</param-name> <param-value>true</param-value> </init-param> </filter>到此,spring下的hibernate多表映射就大功造成了!