为什么80%的码农都做不了架构师?>>>
参考:
1. http://www.iteye.com/topic/1112024 (Hibernate的动态模型)
2. http://man1900.iteye.com/blog/897933 (
Spring与Hibernate动态建表及动态加载映射文件(无需SessionFactory Rebuild)
)(注: 不知道是什么版本的hibernate, 反正4不能用)如果大家有更好的实现方式, 希望能指点.谢谢
正文:
项目中有个需求, 要动态添加表结构, 同时要对其进行增删改查等操作, 而且还会有级联关系.
很多系统中都有这样的需求, 大部分都是使用JDBC自己实现. 最初也想这么做, 但是发现工作量不是一点的. 后来研究了一下hibernate发现如果对hibernate做一些扩展, 还是可以实现的.
涉及的内容:
1. Hibernate的动态模型
原理:
hibernate首先会通过Configuration读取配置文件, 然后进行解析, 最后生成SessionFactory, 当我们操作数据库时通过SessionFactory生成一个Session, 然后再进行对应的操作. 而要实现动态添加表(使用动态模型), 就需要解析XML, 然后将解析出来的内容整合到Session中, 保证在进行增删改查的时候能在Session中取到想要的东西. 因此要实现动态模型就有两个步骤.
1. 解析XML
2. 整合
解析的话, 直接使用
Configuration cfg = new Configuration();
cfg.addFile(new File("Code.hbm.xml"));
cfg.buildMappings();
就可以解析出来了.
但是整合就麻烦了, 因为没有入口, hibernate没有提供现成的接口, 要不然也不会有这篇文章了. 通过查看SessionFactoryImpl源码可以发现, 所有解析configuration都是在构造函数中实现的, 那么, 我可完全可以复制一份构造函数, 然后做一定的改造, 使该方法可以合并Configuration内的东西, 当然可能会遗漏很多东西, 但是我们需求也不多.
在自己工程里创建包org.hibernate.internal, 把hibernate中的SessionFactoryImpl复制到这个包下, 然后对它进行修改, 之所以这样, 是为了让JDK在加载CLASS时替换掉hibernate自带的SessionFatoryImpl, 有些内容没法实现, 如Collections.unmodifiableMap处理过的MAP
代码在最下面, 添加这个方法后, 还要把构造函数里面几处Collections.unmodifiableMap去掉...
DEMO的话大家自己用hibernate里面的DEMO改一下就OK了
注:本方法基于hibernate-core4.2.0.Final
/**
* 合并配置
* @param cfg
* @throws org.hibernate.HibernateException
*/
// @SuppressWarnings( {"unchecked", "ThrowableResultOfMethodCallIgnored"})
public void mergeConfiguration(final Configuration cfg) throws HibernateException {
LOG.debug( "Building session factory" );
Mapping mapping = cfg.buildMapping();
this.properties.putAll( cfg.getProperties() );
this.filters.putAll( cfg.getFilterDefinitions() );
final RegionFactory regionFactory = cacheAccess.getRegionFactory();
LOG.debugf( "Session factory constructed with filter configurations : %s", filters );
LOG.debugf( "Instantiating session factory with properties: %s", properties );
//Generators:
Iterator classes = cfg.getClassMappings();
while ( classes.hasNext() ) {
PersistentClass model = (PersistentClass) classes.next();
if ( !model.isInherited() ) {
IdentifierGenerator generator = model.getIdentifier().createIdentifierGenerator(
cfg.getIdentifierGeneratorFactory(),
getDialect(),
settings.getDefaultCatalogName(),
settings.getDefaultSchemaName(),
(RootClass) model
);
identifierGenerators.put( model.getEntityName(), generator );
}
}
///
// Prepare persisters and link them up with their cache
// region/access-strategy
final String cacheRegionPrefix = settings.getCacheRegionPrefix() == null ? "" : settings.getCacheRegionPrefix() + ".";
final PersisterFactory persisterFactory = serviceRegistry.getService( PersisterFactory.class );
Map entityAccessStrategies = new HashMap();
Map<String,ClassMetadata> classMeta = new HashMap<String,ClassMetadata>();
classes = cfg.getClassMappings();
while ( classes.hasNext() ) {
final PersistentClass model = (PersistentClass) classes.next();
model.prepareTemporaryTables( mapping, getDialect() );
final String cacheRegionName = cacheRegionPrefix + model.getRootClass().getCacheRegionName();
// cache region is defined by the root-class in the hierarchy...
EntityRegionAccessStrategy accessStrategy = ( EntityRegionAccessStrategy ) entityAccessStrategies.get( cacheRegionName );
if ( accessStrategy == null && settings.isSecondLevelCacheEnabled() ) {
final AccessType accessType = AccessType.fromExternalName( model.getCacheConcurrencyStrategy() );
if ( accessType != null ) {
LOG.tracef( "Building shared cache region for entity data [%s]", model.getEntityName() );
// EntityRegion entityRegion = regionFactory.buildEntityRegion( cacheRegionName, properties, CacheDataDescriptionImpl.decode( model ) );
EntityRegion entityRegion = regionFactory.buildEntityRegion(cacheRegionName, properties, CacheDataDescriptionImpl.decode(model));
accessStrategy = entityRegion.buildAccessStrategy( accessType );
entityAccessStrategies.put( cacheRegionName, accessStrategy );
cacheAccess.addCacheRegion( cacheRegionName, entityRegion );
}
}
NaturalIdRegionAccessStrategy naturalIdAccessStrategy = null;
if ( model.hasNaturalId() && model.getNaturalIdCacheRegionName() != null ) {
final String naturalIdCacheRegionName = cacheRegionPrefix + model.getNaturalIdCacheRegionName();
naturalIdAccessStrategy = ( NaturalIdRegionAccessStrategy ) entityAccessStrategies.get( naturalIdCacheRegionName );
if ( naturalIdAccessStrategy == null && settings.isSecondLevelCacheEnabled() ) {
final CacheDataDescriptionImpl cacheDataDescription = CacheDataDescriptionImpl.decode( model );
NaturalIdRegion naturalIdRegion = null;
try {
naturalIdRegion = regionFactory.buildNaturalIdRegion(naturalIdCacheRegionName, properties,
cacheDataDescription);
}
catch ( UnsupportedOperationException e ) {
LOG.warnf(
"Shared cache region factory [%s] does not support natural id caching; " +
"shared NaturalId caching will be disabled for not be enabled for %s",
regionFactory.getClass().getName(),
model.getEntityName()
);
}
if (naturalIdRegion != null) {
naturalIdAccessStrategy = naturalIdRegion.buildAccessStrategy( regionFactory.getDefaultAccessType() );
entityAccessStrategies.put( naturalIdCacheRegionName, naturalIdAccessStrategy );
cacheAccess.addCacheRegion( naturalIdCacheRegionName, naturalIdRegion );
}
}
}
EntityPersister cp = persisterFactory.createEntityPersister(
model,
accessStrategy,
naturalIdAccessStrategy,
this,
mapping
);
entityPersisters.put( model.getEntityName(), cp );
classMeta.put( model.getEntityName(), cp.getClassMetadata() );
}
// this.classMetadata = Collections.unmodifiableMap(classMeta);
for (Map.Entry<String, ClassMetadata> entry : classMeta.entrySet()) {
this.classMetadata.put(entry.getKey(), entry.getValue());
}
Map<String,Set<String>> tmpEntityToCollectionRoleMap = new HashMap<String,Set<String>>();
// collectionPersisters = new HashMap<String,CollectionPersister>();
Map<String,CollectionMetadata> tmpCollectionMetadata = new HashMap<String,CollectionMetadata>();
Iterator collections = cfg.getCollectionMappings();
while ( collections.hasNext() ) {
Collection model = (Collection) collections.next();
final String cacheRegionName = cacheRegionPrefix + model.getCacheRegionName();
final AccessType accessType = AccessType.fromExternalName( model.getCacheConcurrencyStrategy() );
CollectionRegionAccessStrategy accessStrategy = null;
if ( accessType != null && settings.isSecondLevelCacheEnabled() ) {
LOG.tracev( "Building shared cache region for collection data [{0}]", model.getRole() );
CollectionRegion collectionRegion = regionFactory.buildCollectionRegion( cacheRegionName, properties, CacheDataDescriptionImpl
.decode( model ) );
accessStrategy = collectionRegion.buildAccessStrategy( accessType );
entityAccessStrategies.put( cacheRegionName, accessStrategy );
cacheAccess.addCacheRegion( cacheRegionName, collectionRegion );
}
CollectionPersister persister = persisterFactory.createCollectionPersister(
cfg,
model,
accessStrategy,
this
) ;
collectionPersisters.put( model.getRole(), persister );
tmpCollectionMetadata.put( model.getRole(), persister.getCollectionMetadata() );
Type indexType = persister.getIndexType();
if ( indexType != null && indexType.isAssociationType() && !indexType.isAnyType() ) {
String entityName = ( ( AssociationType ) indexType ).getAssociatedEntityName( this );
Set roles = tmpEntityToCollectionRoleMap.get( entityName );
if ( roles == null ) {
roles = new HashSet();
tmpEntityToCollectionRoleMap.put( entityName, roles );
}
roles.add( persister.getRole() );
}
Type elementType = persister.getElementType();
if ( elementType.isAssociationType() && !elementType.isAnyType() ) {
String entityName = ( ( AssociationType ) elementType ).getAssociatedEntityName( this );
Set roles = tmpEntityToCollectionRoleMap.get( entityName );
if ( roles == null ) {
roles = new HashSet();
tmpEntityToCollectionRoleMap.put( entityName, roles );
}
roles.add( persister.getRole() );
}
}
// collectionMetadata = Collections.unmodifiableMap( tmpCollectionMetadata );
for (Map.Entry<String, CollectionMetadata> entry : tmpCollectionMetadata.entrySet()) {
collectionMetadata.put(entry.getKey(), entry.getValue());
}
Iterator itr = tmpEntityToCollectionRoleMap.entrySet().iterator();
while ( itr.hasNext() ) {
final Map.Entry entry = ( Map.Entry ) itr.next();
entry.setValue( Collections.unmodifiableSet( ( Set ) entry.getValue() ) );
}
// collectionRolesByEntityParticipant = Collections.unmodifiableMap( tmpEntityToCollectionRoleMap );
for (Map.Entry<String, Set<String>> entry : tmpEntityToCollectionRoleMap.entrySet()) {
collectionRolesByEntityParticipant.put(entry.getKey(), entry.getValue());
}
//Named Queries:
namedQueries.putAll(cfg.getNamedQueries());
namedSqlQueries.putAll(cfg.getNamedSQLQueries());
sqlResultSetMappings.putAll(cfg.getSqlResultSetMappings());
imports.putAll(cfg.getImports());
// after *all* persisters and named queries are registered
Iterator iter = entityPersisters.values().iterator();
while ( iter.hasNext() ) {
final EntityPersister persister = ( ( EntityPersister ) iter.next() );
persister.postInstantiate();
registerEntityNameResolvers( persister );
}
iter = collectionPersisters.values().iterator();
while ( iter.hasNext() ) {
final CollectionPersister persister = ( ( CollectionPersister ) iter.next() );
persister.postInstantiate();
}
//JNDI + Serialization:
// name = settings.getSessionFactoryName();
// try {
// uuid = (String) UUID_GENERATOR.generate(null, null);
// }
// catch (Exception e) {
// throw new AssertionFailure("Could not generate UUID");
// }
// SessionFactoryRegistry.INSTANCE.addSessionFactory(
// uuid,
// name,
// settings.isSessionFactoryNameAlsoJndiName(),
// this,
// serviceRegistry.getService( JndiService.class )
// );
LOG.debug( "Instantiated session factory" );
settings.getMultiTableBulkIdStrategy().prepare(
jdbcServices,
buildLocalConnectionAccess(),
cfg.createMappings(),
cfg.buildMapping(),
properties
);
if ( settings.isAutoCreateSchema() ) {
new SchemaExport( serviceRegistry, cfg )
.setImportSqlCommandExtractor( serviceRegistry.getService( ImportSqlCommandExtractor.class ) )
.create( false, true );
}
if ( settings.isAutoUpdateSchema() ) {
new SchemaUpdate( serviceRegistry, cfg ).execute( false, true );
}
if ( settings.isAutoValidateSchema() ) {
new SchemaValidator( serviceRegistry, cfg ).validate();
}
if ( settings.isAutoDropSchema() ) {
schemaExport = new SchemaExport( serviceRegistry, cfg )
.setImportSqlCommandExtractor( serviceRegistry.getService( ImportSqlCommandExtractor.class ) );
}
// currentSessionContext = buildCurrentSessionContext();
//checking for named queries
if ( settings.isNamedQueryStartupCheckingEnabled() ) {
final Map<String,HibernateException> errors = checkNamedQueries();
if ( ! errors.isEmpty() ) {
StringBuilder failingQueries = new StringBuilder( "Errors in named queries: " );
String sep = "";
for ( Map.Entry<String,HibernateException> entry : errors.entrySet() ) {
LOG.namedQueryError( entry.getKey(), entry.getValue() );
failingQueries.append( sep ).append( entry.getKey() );
sep = ", ";
}
throw new HibernateException( failingQueries.toString() );
}
}
// this needs to happen after persisters are all ready to go...
// this.fetchProfiles = new HashMap();
itr = cfg.iterateFetchProfiles();
while ( itr.hasNext() ) {
final org.hibernate.mapping.FetchProfile mappingProfile =
( org.hibernate.mapping.FetchProfile ) itr.next();
final FetchProfile fetchProfile = new FetchProfile( mappingProfile.getName() );
for ( org.hibernate.mapping.FetchProfile.Fetch mappingFetch : mappingProfile.getFetches() ) {
// resolve the persister owning the fetch
final String entityName = getImportedClassName( mappingFetch.getEntity() );
final EntityPersister owner = entityName == null
? null
: entityPersisters.get( entityName );
if ( owner == null ) {
throw new HibernateException(
"Unable to resolve entity reference [" + mappingFetch.getEntity()
+ "] in fetch profile [" + fetchProfile.getName() + "]"
);
}
// validate the specified association fetch
Type associationType = owner.getPropertyType( mappingFetch.getAssociation() );
if ( associationType == null || !associationType.isAssociationType() ) {
throw new HibernateException( "Fetch profile [" + fetchProfile.getName() + "] specified an invalid association" );
}
// resolve the style
final Fetch.Style fetchStyle = Fetch.Style.parse( mappingFetch.getStyle() );
// then construct the fetch instance...
fetchProfile.addFetch( new Association( owner, mappingFetch.getAssociation() ), fetchStyle );
((Loadable) owner).registerAffectingFetchProfile( fetchProfile.getName() );
}
fetchProfiles.put( fetchProfile.getName(), fetchProfile );
}
// this.customEntityDirtinessStrategy = determineCustomEntityDirtinessStrategy();
// this.currentTenantIdentifierResolver = determineCurrentTenantIdentifierResolver( cfg.getCurrentTenantIdentifierResolver() );
// this.transactionEnvironment = new TransactionEnvironmentImpl( this );
// this.observer.sessionFactoryCreated( this );
}
使用:
private SessionFactory sessionFactory;
@Override
protected void setUp() throws Exception {
// A SessionFactory is set up once for an application
sessionFactory = new Configuration()
.configure() // configures settings from hibernate.cfg.xml
.buildSessionFactory();
}
@Override
protected void tearDown() throws Exception {
if ( sessionFactory != null ) {
sessionFactory.close();
}
}
public void testBasicCode() {
SessionFactoryImpl sf = (SessionFactoryImpl) sessionFactory;
Configuration cfg = new Configuration();
cfg.addFile(new File("Code.hbm.xml"));
cfg.buildMappings();
sf.mergeConfiguration(cfg);
Session session = sf.openSession();
session.beginTransaction();
Map code = new HashMap();
code.put("name", "jk");
code.put("title", "哈哈");
session.save("Code", code);
session.getTransaction().commit();
session = sf.openSession();
session.beginTransaction();
List result = session.createQuery("from Code").list();
for (Object obj : result) {
System.out.println("Code ("+obj+")");
}
session.getTransaction().commit();
}