posts - 40, comments - 58, trackbacks - 0, articles - 0
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理
最近开发的一个系统,需要在不更改代码和重启系统的情况下提供对用户自动建表的支持,由于系统应用了hibernate,所以在建表同时也要建立持久化对象以及对这些对象注册,人渣我首先想倒的是 baidu和google,哪知一番搜索下来,发现都不尽入人意,于是乎,造轮子之路开始了 
数据库我是采用的oracle9i,目前在比如数据库类型支持,还有对象关系支持上都很简单,不过在现有基础上进行扩展,都是可以实现的 
实现步骤如下 
建立class->生成hbm.xml->在Hibernate'config里面注册持久化类->通知SessionFactory持久化类的新增 
1 准备 
首先准备基础数据,我建立了几个类来对生成的表和属性做描述 这些描述都将作为传输传递给class生成方法和hbm.xml的生成方法 
RenderClass 描述要生成的实体类 属性如下 
  • className 类名
  • tableName 对应表名
  • properties 属性集合

RenderProperty 就是properties集合的内容 属性如下 
  • name 属性名称
  • type java类型
  • field 字段名
  • primary 是否主键
  • sequence 对应ID生成的sequence
  • length 对应长度

2 生成class 
采用ASM 生成 
Java代码 
  1. /** 
  2.  * 根据传入参数创建CLASS文件 
  3.  * 
  4.  * @param 类名 
  5.  * @param 保存路径 
  6.  * @param 属性描述 
  7.  * @return Class 
  8.  */  
  9. public  Class build(String clsname,String savepath,Collection properties)  
  10. {  
  11.     Class cls = null;  
  12.     try  
  13.     {  
  14.         String classname = BuildUtil.transferClassName(clsname);  
  15.         ClassWriter cw = new ClassWriter(false);     
  16.         //建立构造函数  
  17.         cw.visit(V1_1, ACC_PUBLIC, classname, null"java/lang/Object"null);     
  18.         MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "<init>""()V"nullnull);     
  19.         mw.visitVarInsn(ALOAD, 0);     
  20.         mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object""<init>""()V");     
  21.         mw.visitInsn(RETURN);     
  22.         mw.visitMaxs(11);     
  23.         mw.visitEnd();  
  24.           
  25.         BuildProperty property = null;  
  26.         String propertytype = null;  
  27.         String propertyname = null;;  
  28.         //建立属性  
  29.         Iterator iterator = properties.iterator();  
  30.         while (iterator.hasNext())  
  31.         {  
  32.             //建立属性对应的类变量  
  33.             property = (BuildProperty)iterator.next();  
  34.             propertytype = BuildUtil.transferClassName(property.getType());  
  35.             propertyname = WordUtils.capitalize(property.getName());  
  36.             cw.visitField(ACC_PRIVATE, property.getName(), "L"+propertytype+";"nullnull).visitEnd();  
  37.             //建立get方法  
  38.             mw = cw.visitMethod(ACC_PUBLIC, "get"+propertyname, "()L"+propertytype+";"null,   
  39.             null);   
  40.             mw.visitCode();   
  41.             mw.visitVarInsn(ALOAD, 0);   
  42.             mw   
  43.             .visitFieldInsn(GETFIELD, classname, property.getName(),   
  44.             "L"+propertytype+";");   
  45.             mw.visitInsn(ARETURN);   
  46.             mw.visitMaxs(11);   
  47.             mw.visitEnd();   
  48.             //建立set方法  
  49.             mw = cw.visitMethod(ACC_PUBLIC, "set"+propertyname, "(L"+propertytype+";)V",   
  50.                     nullnull);   
  51.             mw.visitCode();   
  52.             mw.visitVarInsn(ALOAD, 0);   
  53.             mw.visitVarInsn(ALOAD, 1);   
  54.             mw   
  55.             .visitFieldInsn(PUTFIELD, classname, property.getName(),   
  56.             "L"+propertytype+";");   
  57.             mw.visitMaxs(22);   
  58.             mw.visitInsn(RETURN);   
  59.             mw.visitEnd();   
  60.         }  
  61.           
  62.         cw.visitEnd();  
  63.   
  64.         byte[] code = cw.toByteArray();   
  65.         if (savepath!=null)  
  66.         {    
  67.             Assistant.createNewFile(savepath);  
  68.             FileOutputStream fos = new FileOutputStream(savepath);     
  69.             fos.write(code);     
  70.             fos.close();     
  71.         }  
  72.         cls = this.defineClass(clsname, code, 0, code.length);  
  73.         return cls;  
  74.     }  
  75.     catch (Throwable e)  
  76.     {  
  77.         e.printStackTrace();  
  78.     }  
  79.     return cls;  
  80. }  

3 生成hbm.xml 
生成这种事情,当然是交给模板了,我这里用的是Freemarker,在这里,我要感谢freemarker的开发组,感谢webwork 感谢XXCMS系统对freemarker以及其他模板语言的大力推广,,感谢。。。。(省略100字) 
模板嘛,肯定不仅仅是应用在web环境的,看看源代码下的freemarker.cache包,恩,不错,freemarker团队确实是有一定“野心”的 首先是渲染的实现 
Java代码 
  1. package com.mit.cooperate.core.asm.render;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileOutputStream;  
  5. import java.io.PrintWriter;  
  6. import java.io.StringWriter;  
  7.   
  8. import javax.servlet.ServletContext;  
  9.   
  10. import com.mit.cooperate.core.util.Assistant;  
  11.   
  12. import freemarker.cache.ClassTemplateLoader;  
  13. import freemarker.template.Configuration;  
  14. import freemarker.template.ObjectWrapper;  
  15. import freemarker.template.SimpleHash;  
  16. import freemarker.template.Template;  
  17. import freemarker.template.TemplateExceptionHandler;  
  18. /** 
  19.  * freeMarker模板渲染 
  20.  * @author courser.tijichen 
  21.  */  
  22. public class FreemarkerRender implements Render{  
  23.       
  24.     private Configuration templateconfig;  
  25.       
  26.     public  FreemarkerRender()  
  27.     {  
  28.         this.initialize();  
  29.     }  
  30.     /** 
  31.      * 初始化freemarker配置 
  32.      * 
  33.      */  
  34.     public void initialize()  
  35.     {  
  36.         try  
  37.         {  
  38.             if (templateconfig==null)  
  39.             {  
  40.                 templateconfig = new Configuration();  
  41.                 templateconfig.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER);  
  42.                 templateconfig.setObjectWrapper(ObjectWrapper.DEFAULT_WRAPPER);  
  43.                 templateconfig.setTemplateLoader(new ClassTemplateLoader(this.getClass(),"/"));  
  44.                 templateconfig.setTemplateUpdateDelay(1200);  
  45.                 templateconfig.setDefaultEncoding("gb2312");  
  46.                 templateconfig.setLocale(new java.util.Locale("zh_CN"));  
  47.                 templateconfig.setNumberFormat("0.##########");  
  48.             }  
  49.         }  
  50.         catch (Exception e)  
  51.         {}  
  52.     }  
  53.     /** 
  54.      * 渲染 
  55.      * 
  56.      */  
  57.     public void render(RenderClass target,String template,String outpath)  
  58.     {  
  59.         try  
  60.         {  
  61.             StringWriter writer = new StringWriter();  
  62.             Template tl = templateconfig.getTemplate(  
  63.                     template,  
  64.                     templateconfig.getLocale());  
  65.             SimpleHash root = new SimpleHash();  
  66.             root.put("entity", target);  
  67.             tl.process(root, writer);  
  68.             Assistant.createNewFile(outpath);  
  69.             FileOutputStream fos = new FileOutputStream(outpath);  
  70.             PrintWriter pwriter = new PrintWriter(fos);  
  71.             pwriter.write(writer.toString());  
  72.             pwriter.close();  
  73.             fos.close();     
  74.         }  
  75.         catch (Exception ex)  
  76.         {  
  77.             ex.printStackTrace();  
  78.         }  
  79.     }  
  80. }  

然后就是准备模板了,首先声明,我这个模板写得简单粗陋之极,不过粗陋简单不正是人渣偶的终极处世之道么,何况只实现几个简单功能,仅仅是针对测试而已嘛,何必搞得那么正规严肃了,嘿嘿 
Html代码 
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
  3. "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
  4. <hibernate-mapping>  
  5.     <class name="${entity.className}" table="${entity.tableName}">  
  6.         <#if entity.properties?exists>  
  7.             <#list entity.properties as property>  
  8.                 <#if property.primary>  
  9.                     <id name="${property.name}" type="${property.type}">  
  10.                 <#else>  
  11.                     <property name="${property.name}" type="${property.type}">  
  12.                 </#if>  
  13.                 <#if property.type=="java.lang.String">  
  14.                     <column name="${property.field?upper_case}" <#if property.length?exists>length="${property.length}"</#if>></column>  
  15.                 <#elseif property.type=="java.util.Date">  
  16.                         <column name="${property.field?upper_case}" length=7></column>  
  17.                 <#elseif property.type=="java.lang.Long" || property.type=="java.lang.Integer"  
  18.                     || property.type=="java.lang.Short">  
  19.                         <column name="${property.field?upper_case}" <#if property.length?exists>precision="${property.length}"</#if> scale="0"></column>  
  20.                 </#if>  
  21.                 <#if property.primary==true>  
  22.                     <#if property.sequence?exists>  
  23.                         <generator class="sequence">  
  24.                             <param name="sequence">${property.sequence}</param>  
  25.                         </generator>  
  26.                     </#if>  
  27.                     </id>  
  28.                 <#else>  
  29.                     </property>  
  30.                 </#if>  
  31.             </#list>  
  32.         </#if>  
  33.     </class>  
  34. </hibernate-mapping>  

4 注册 
首先是对生成的hbm.xml的注册,比如,我获取倒Hibernate的一个config以后 
Java代码 
  1. URL  url = this.getClass().getResource("/com/mit/test/person.hbm.xml");  
  2. config.addURL(url);  

然后就是要通知sessionFactory,新增了持久类,目前很多方法都是重启sessionfactory,就是关闭当前 sessionFactory ,然后根据config build一个新的sessionFactory出来,但是,这种情况感觉总不那么完美,虽然这个过程持续不到多长时间,但用户每增一个表就close然后build一个,单说用户体验,人家正在提交数据了,你把这个给close了.... 
但目前hibernate包的 sessionFactory 确实没提供这种对持久类的add支持,XX伟人说过:没有条件 创造条件也要上,于是乎,拿起你的键盘,启动 editplus,向hibernate3的源码砍去。。 

其实,改动地方也不大。 
Java代码 
  1. 一是,改动Configuration,三句话  
  2.     public Mapping getMapping()  
  3.     {  
  4.         return this.mapping;  
  5.     }  

然后是SessionFactoryImpl 我们要让他知道,这个世界上还存在很多未知的来客,需要你去主动了解。。。。 
增加代码如下 
Java代码 
  1. public void addPersistentClass(PersistentClass model,Mapping mapping)  
  2. {  
  3.     if ( !model.isInherited() ) {  
  4.         IdentifierGenerator generator = model.getIdentifier().createIdentifierGenerator(  
  5.                 settings.getDialect(),  
  6.                 settings.getDefaultCatalogName(),  
  7.                 settings.getDefaultSchemaName(),  
  8.                 (RootClass) model  
  9.         );  
  10.         if (!identifierGenerators.containsKey(model.getEntityName()))  
  11.             identifierGenerators.put( model.getEntityName(), generator );  
  12.     }  
  13.       
  14.     model.prepareTemporaryTables( mapping, settings.getDialect() );  
  15.     String cacheRegion = model.getRootClass().getCacheRegionName();  
  16.   
  17.     CacheConcurrencyStrategy cache = CacheFactory.createCache(  
  18.                 model.getCacheConcurrencyStrategy(),  
  19.                 cacheRegion,  
  20.                 model.isMutable(),  
  21.                 settings,  
  22.                 properties  
  23.             );  
  24.     if (cache!=null)  
  25.         allCacheRegions.put( cache.getRegionName(), cache.getCache() );  
  26.           
  27.       
  28.     EntityPersister cp = PersisterFactory.createClassPersister(model, cache, this, mapping);  
  29.     if ( cache != null && cache.getCache() instanceof OptimisticCache ) {  
  30.         ( ( OptimisticCache ) cache.getCache() ).setSource( cp );  
  31.     }  
  32.     entityPersisters.put( model.getEntityName(), cp );  
  33. }  

最后遗留的是hbm.xml在cfg.xml的注册,以保证系统重启后可以顺利加载新增的持久化配置 
Java代码 
  1. /** 
  2.  * 把hbm.xml的路径加入到cfg.xml的mapping结点 
  3.  * 
  4.  * @param cfg.xml的路径 
  5.  * @param hbm.xml的路径 
  6.  */  
  7. public static void updateHbmCfg(URL url,String hbm)  
  8. {  
  9.     try  
  10.     {  
  11.         SAXReader reader = new SAXReader();  
  12.         Document doc = reader.read(url);  
  13.         Element element = (Element)doc.getRootElement()  
  14.         .selectSingleNode("session-factory");  
  15.           
  16.         Element hbmnode = element.addElement("mapping");  
  17.         hbmnode.addAttribute("resource", hbm);  
  18.         String filepath = url.getFile();  
  19.         if (filepath.charAt(0)=='/')  
  20.             filepath = filepath.substring(1);  
  21.         FileOutputStream outputstream = new FileOutputStream(filepath);  
  22.         XMLWriter writer = new XMLWriter(outputstream);  
  23.         writer.write(doc);  
  24.         outputstream.close();  
  25.     }  
  26.     catch (Exception e)  
  27.     {  
  28.         e.printStackTrace();  
  29.     }  
  30. }  

Java代码 
  1. 大功告成 ,先运行一个小周天  

最后是总结测试,写个junit 搞定 代码如下: 
Java代码 
  1. package com.mit.cooperate.core.hibernate;  
  2.   
  3. import junit.framework.TestCase;  
  4.   
  5. import java.net.URL;  
  6. import java.util.ArrayList;  
  7.   
  8. import org.apache.commons.beanutils.PropertyUtils;  
  9.   
  10. import org.hibernate.Session;  
  11. import org.hibernate.cfg.Configuration;  
  12. import org.hibernate.SessionFactory;  
  13. import org.hibernate.tool.hbm2ddl.SchemaExport;  
  14. import org.hibernate.impl.SessionFactoryImpl;  
  15. import org.hibernate.mapping.PersistentClass;  
  16. import org.hibernate.Transaction;  
  17.   
  18. import com.mit.cooperate.core.asm.*;  
  19. import com.mit.cooperate.core.asm.render.*;  
  20.   
  21. public class HibernateTest extends TestCase {  
  22.       
  23.     private Configuration config;  
  24.     private SessionFactory factory;  
  25.       
  26.     public void setUp()  
  27.     {  
  28.         URL  url = this.getClass().getResource("/com/mit/cooperate/core/hibernate/hibernate.cfg.xml");  
  29.         config = new Configuration().configure(url);  
  30.         factory = config.buildSessionFactory();  
  31.     }  
  32.       
  33.     public void testBuild() throws Exception  
  34.     {  
  35.         //持久类对象描述  
  36.         RenderClass rc = new RenderClass();  
  37.         ArrayList list = new ArrayList();  
  38.           
  39.         RenderProperty property = new RenderProperty();  
  40.         //添加主键  
  41.         property.setName("oid");  
  42.         property.setField("oid");  
  43.         property.setLength(new Integer(15));  
  44.         property.setPrimary(true);  
  45.         property.setType(Long.class.getName());  
  46.         property.setSequence("SEQ_PERSON");  
  47.           
  48.         list.add(property);  
  49.         //添加一个name字段  
  50.         property = new RenderProperty();  
  51.         property.setName("name");  
  52.         property.setType(String.class.getName());  
  53.         property.setField("name");  
  54.         property.setLength(new Integer(20));  
  55.           
  56.         list.add(property);  
  57.           
  58.         rc.setProperties(list);  
  59.         //类名  
  60.         rc.setClassName("com.mit.test.Person");  
  61.         rc.setTableName("person");  
  62.         //开始生成class  
  63.         POBuildUtil util = new POBuildUtil();  
  64.         util.build(rc.getClassName(),"E:\\cpc\\source\\cooperateCore\\com\\mit\\test\\Person.class",list);  
  65.         //实例化一个person  
  66.         Object person = Class.forName("com.mit.test.Person").newInstance();//hbmcls.newInstance();  
  67.           
  68.         //开始生成hbm.xml  
  69.         FreemarkerRender render = new FreemarkerRender();  
  70.         render.render(rc, Templates.TEMPLATE_HIBERNATE3, "E:\\cpc\\source\\cooperateCore\\com\\mit\\test\\person.hbm.xml");  
  71.         URL  url = this.getClass().getResource("/com/mit/test/person.hbm.xml");  
  72.         config.addURL(url);  
  73.         //更新hibernate.cfg.xml  
  74.         HibernateUtil.updateHbmCfg( this.getClass().getResource("/com/mit/cooperate/core/hibernate/hibernate.cfg.xml"), "com/mit/test/person.hbm.xml");  
  75.           
  76.         PersistentClass model = config.getClassMapping("com.mit.test.Person");  
  77.         //sessionFactory哪下子,快接纳person爷爷进去  
  78.         ((SessionFactoryImpl)factory).addPersistentClass(model, config.getMapping());  
  79.         //生成数据库  
  80.         SchemaExport export = new SchemaExport(config,((SessionFactoryImpl)factory).getSettings());  
  81.         export.execute(truetrue,false,true);  
  82.         //测试一下,随便给个名字什么的  
  83.         PropertyUtils.setProperty(person, "name""chenzhi");  
  84.         Session session = factory.openSession();  
  85.         Transaction tran = session.beginTransaction();  
  86.         try  
  87.         {  
  88.             //保存  
  89.             session.save(person);  
  90.             tran.commit();  
  91.         }  
  92.         catch (Exception e)  
  93.         {  
  94.             e.printStackTrace();  
  95.             tran.rollback();  
  96.         }  
  97.         finally  
  98.         {  
  99.             session.close();  
  100.         }  
  101.     }  
  102.       
  103.     public void tearDown()  
  104.     {  
  105.         factory.close();  
  106.     }  
  107.       
  108.       
  109. }  

评论

# re: Hibernate自定义表单完全解决方案(无需重置SessionFactory)  回复  更多评论   

2009-10-26 21:28 by bus387
非常棒。你的这种实现为不重启刷新sessionFactory提供了一种非常好的思路。顶你。

只有注册用户登录后才能发表评论。


网站导航: