|
Posted on 2009-10-26 13:16 Astro.Qi 阅读(596) 评论(1) 编辑 收藏 所属分类: Hibernate
最近开发的一个系统,需要在不更改代码和重启系统的情况下提供对用户自动建表的支持,由于系统应用了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 生成
- /**
- * 根据传入参数创建CLASS文件
- *
- * @param 类名
- * @param 保存路径
- * @param 属性描述
- * @return Class
- */
- public Class build(String clsname,String savepath,Collection properties)
- {
- Class cls = null;
- try
- {
- String classname = BuildUtil.transferClassName(clsname);
- ClassWriter cw = new ClassWriter(false);
- //建立构造函数
- cw.visit(V1_1, ACC_PUBLIC, classname, null, "java/lang/Object", null);
- MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
- mw.visitVarInsn(ALOAD, 0);
- mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
- mw.visitInsn(RETURN);
- mw.visitMaxs(1, 1);
- mw.visitEnd();
-
- BuildProperty property = null;
- String propertytype = null;
- String propertyname = null;;
- //建立属性
- Iterator iterator = properties.iterator();
- while (iterator.hasNext())
- {
- //建立属性对应的类变量
- property = (BuildProperty)iterator.next();
- propertytype = BuildUtil.transferClassName(property.getType());
- propertyname = WordUtils.capitalize(property.getName());
- cw.visitField(ACC_PRIVATE, property.getName(), "L"+propertytype+";", null, null).visitEnd();
- //建立get方法
- mw = cw.visitMethod(ACC_PUBLIC, "get"+propertyname, "()L"+propertytype+";", null,
- null);
- mw.visitCode();
- mw.visitVarInsn(ALOAD, 0);
- mw
- .visitFieldInsn(GETFIELD, classname, property.getName(),
- "L"+propertytype+";");
- mw.visitInsn(ARETURN);
- mw.visitMaxs(1, 1);
- mw.visitEnd();
- //建立set方法
- mw = cw.visitMethod(ACC_PUBLIC, "set"+propertyname, "(L"+propertytype+";)V",
- null, null);
- mw.visitCode();
- mw.visitVarInsn(ALOAD, 0);
- mw.visitVarInsn(ALOAD, 1);
- mw
- .visitFieldInsn(PUTFIELD, classname, property.getName(),
- "L"+propertytype+";");
- mw.visitMaxs(2, 2);
- mw.visitInsn(RETURN);
- mw.visitEnd();
- }
-
- cw.visitEnd();
-
- byte[] code = cw.toByteArray();
- if (savepath!=null)
- {
- Assistant.createNewFile(savepath);
- FileOutputStream fos = new FileOutputStream(savepath);
- fos.write(code);
- fos.close();
- }
- cls = this.defineClass(clsname, code, 0, code.length);
- return cls;
- }
- catch (Throwable e)
- {
- e.printStackTrace();
- }
- return cls;
- }
3 生成hbm.xml
生成这种事情,当然是交给模板了,我这里用的是Freemarker,在这里,我要感谢freemarker的开发组,感谢webwork 感谢XXCMS系统对freemarker以及其他模板语言的大力推广,,感谢。。。。(省略100字)
模板嘛,肯定不仅仅是应用在web环境的,看看源代码下的freemarker.cache包,恩,不错,freemarker团队确实是有一定“野心”的 首先是渲染的实现
- package com.mit.cooperate.core.asm.render;
-
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.PrintWriter;
- import java.io.StringWriter;
-
- import javax.servlet.ServletContext;
-
- import com.mit.cooperate.core.util.Assistant;
-
- import freemarker.cache.ClassTemplateLoader;
- import freemarker.template.Configuration;
- import freemarker.template.ObjectWrapper;
- import freemarker.template.SimpleHash;
- import freemarker.template.Template;
- import freemarker.template.TemplateExceptionHandler;
- /**
- * freeMarker模板渲染
- * @author courser.tijichen
- */
- public class FreemarkerRender implements Render{
-
- private Configuration templateconfig;
-
- public FreemarkerRender()
- {
- this.initialize();
- }
- /**
- * 初始化freemarker配置
- *
- */
- public void initialize()
- {
- try
- {
- if (templateconfig==null)
- {
- templateconfig = new Configuration();
- templateconfig.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER);
- templateconfig.setObjectWrapper(ObjectWrapper.DEFAULT_WRAPPER);
- templateconfig.setTemplateLoader(new ClassTemplateLoader(this.getClass(),"/"));
- templateconfig.setTemplateUpdateDelay(1200);
- templateconfig.setDefaultEncoding("gb2312");
- templateconfig.setLocale(new java.util.Locale("zh_CN"));
- templateconfig.setNumberFormat("0.##########");
- }
- }
- catch (Exception e)
- {}
- }
- /**
- * 渲染
- *
- */
- public void render(RenderClass target,String template,String outpath)
- {
- try
- {
- StringWriter writer = new StringWriter();
- Template tl = templateconfig.getTemplate(
- template,
- templateconfig.getLocale());
- SimpleHash root = new SimpleHash();
- root.put("entity", target);
- tl.process(root, writer);
- Assistant.createNewFile(outpath);
- FileOutputStream fos = new FileOutputStream(outpath);
- PrintWriter pwriter = new PrintWriter(fos);
- pwriter.write(writer.toString());
- pwriter.close();
- fos.close();
- }
- catch (Exception ex)
- {
- ex.printStackTrace();
- }
- }
- }
然后就是准备模板了,首先声明,我这个模板写得简单粗陋之极,不过粗陋简单不正是人渣偶的终极处世之道么,何况只实现几个简单功能,仅仅是针对测试而已嘛,何必搞得那么正规严肃了,嘿嘿
- <?xml version="1.0" encoding="utf-8"?>
- <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
- "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
- <hibernate-mapping>
- <class name="${entity.className}" table="${entity.tableName}">
- <#if entity.properties?exists>
- <#list entity.properties as property>
- <#if property.primary>
- <id name="${property.name}" type="${property.type}">
- <#else>
- <property name="${property.name}" type="${property.type}">
- </#if>
- <#if property.type=="java.lang.String">
- <column name="${property.field?upper_case}" <#if property.length?exists>length="${property.length}"</#if>></column>
- <#elseif property.type=="java.util.Date">
- <column name="${property.field?upper_case}" length=7></column>
- <#elseif property.type=="java.lang.Long" || property.type=="java.lang.Integer"
- || property.type=="java.lang.Short">
- <column name="${property.field?upper_case}" <#if property.length?exists>precision="${property.length}"</#if> scale="0"></column>
- </#if>
- <#if property.primary==true>
- <#if property.sequence?exists>
- <generator class="sequence">
- <param name="sequence">${property.sequence}</param>
- </generator>
- </#if>
- </id>
- <#else>
- </property>
- </#if>
- </#list>
- </#if>
- </class>
- </hibernate-mapping>
4 注册
首先是对生成的hbm.xml的注册,比如,我获取倒Hibernate的一个config以后
- URL url = this.getClass().getResource("/com/mit/test/person.hbm.xml");
- config.addURL(url);
然后就是要通知sessionFactory,新增了持久类,目前很多方法都是重启sessionfactory,就是关闭当前 sessionFactory ,然后根据config build一个新的sessionFactory出来,但是,这种情况感觉总不那么完美,虽然这个过程持续不到多长时间,但用户每增一个表就close然后build一个,单说用户体验,人家正在提交数据了,你把这个给close了....
但目前hibernate包的 sessionFactory 确实没提供这种对持久类的add支持,XX伟人说过:没有条件 创造条件也要上,于是乎,拿起你的键盘,启动 editplus,向hibernate3的源码砍去。。
其实,改动地方也不大。
- 一是,改动Configuration,三句话
- public Mapping getMapping()
- {
- return this.mapping;
- }
然后是SessionFactoryImpl 我们要让他知道,这个世界上还存在很多未知的来客,需要你去主动了解。。。。
增加代码如下
- public void addPersistentClass(PersistentClass model,Mapping mapping)
- {
- if ( !model.isInherited() ) {
- IdentifierGenerator generator = model.getIdentifier().createIdentifierGenerator(
- settings.getDialect(),
- settings.getDefaultCatalogName(),
- settings.getDefaultSchemaName(),
- (RootClass) model
- );
- if (!identifierGenerators.containsKey(model.getEntityName()))
- identifierGenerators.put( model.getEntityName(), generator );
- }
-
- model.prepareTemporaryTables( mapping, settings.getDialect() );
- String cacheRegion = model.getRootClass().getCacheRegionName();
-
- CacheConcurrencyStrategy cache = CacheFactory.createCache(
- model.getCacheConcurrencyStrategy(),
- cacheRegion,
- model.isMutable(),
- settings,
- properties
- );
- if (cache!=null)
- allCacheRegions.put( cache.getRegionName(), cache.getCache() );
-
-
- EntityPersister cp = PersisterFactory.createClassPersister(model, cache, this, mapping);
- if ( cache != null && cache.getCache() instanceof OptimisticCache ) {
- ( ( OptimisticCache ) cache.getCache() ).setSource( cp );
- }
- entityPersisters.put( model.getEntityName(), cp );
- }
最后遗留的是hbm.xml在cfg.xml的注册,以保证系统重启后可以顺利加载新增的持久化配置
- /**
- * 把hbm.xml的路径加入到cfg.xml的mapping结点
- *
- * @param cfg.xml的路径
- * @param hbm.xml的路径
- */
- public static void updateHbmCfg(URL url,String hbm)
- {
- try
- {
- SAXReader reader = new SAXReader();
- Document doc = reader.read(url);
- Element element = (Element)doc.getRootElement()
- .selectSingleNode("session-factory");
-
- Element hbmnode = element.addElement("mapping");
- hbmnode.addAttribute("resource", hbm);
- String filepath = url.getFile();
- if (filepath.charAt(0)=='/')
- filepath = filepath.substring(1);
- FileOutputStream outputstream = new FileOutputStream(filepath);
- XMLWriter writer = new XMLWriter(outputstream);
- writer.write(doc);
- outputstream.close();
- }
- catch (Exception e)
- {
- e.printStackTrace();
- }
- }
最后是总结测试,写个junit 搞定 代码如下:
- package com.mit.cooperate.core.hibernate;
-
- import junit.framework.TestCase;
-
- import java.net.URL;
- import java.util.ArrayList;
-
- import org.apache.commons.beanutils.PropertyUtils;
-
- import org.hibernate.Session;
- import org.hibernate.cfg.Configuration;
- import org.hibernate.SessionFactory;
- import org.hibernate.tool.hbm2ddl.SchemaExport;
- import org.hibernate.impl.SessionFactoryImpl;
- import org.hibernate.mapping.PersistentClass;
- import org.hibernate.Transaction;
-
- import com.mit.cooperate.core.asm.*;
- import com.mit.cooperate.core.asm.render.*;
-
- public class HibernateTest extends TestCase {
-
- private Configuration config;
- private SessionFactory factory;
-
- public void setUp()
- {
- URL url = this.getClass().getResource("/com/mit/cooperate/core/hibernate/hibernate.cfg.xml");
- config = new Configuration().configure(url);
- factory = config.buildSessionFactory();
- }
-
- public void testBuild() throws Exception
- {
- //持久类对象描述
- RenderClass rc = new RenderClass();
- ArrayList list = new ArrayList();
-
- RenderProperty property = new RenderProperty();
- //添加主键
- property.setName("oid");
- property.setField("oid");
- property.setLength(new Integer(15));
- property.setPrimary(true);
- property.setType(Long.class.getName());
- property.setSequence("SEQ_PERSON");
-
- list.add(property);
- //添加一个name字段
- property = new RenderProperty();
- property.setName("name");
- property.setType(String.class.getName());
- property.setField("name");
- property.setLength(new Integer(20));
-
- list.add(property);
-
- rc.setProperties(list);
- //类名
- rc.setClassName("com.mit.test.Person");
- rc.setTableName("person");
- //开始生成class
- POBuildUtil util = new POBuildUtil();
- util.build(rc.getClassName(),"E:\\cpc\\source\\cooperateCore\\com\\mit\\test\\Person.class",list);
- //实例化一个person
- Object person = Class.forName("com.mit.test.Person").newInstance();//hbmcls.newInstance();
-
- //开始生成hbm.xml
- FreemarkerRender render = new FreemarkerRender();
- render.render(rc, Templates.TEMPLATE_HIBERNATE3, "E:\\cpc\\source\\cooperateCore\\com\\mit\\test\\person.hbm.xml");
- URL url = this.getClass().getResource("/com/mit/test/person.hbm.xml");
- config.addURL(url);
- //更新hibernate.cfg.xml
- HibernateUtil.updateHbmCfg( this.getClass().getResource("/com/mit/cooperate/core/hibernate/hibernate.cfg.xml"), "com/mit/test/person.hbm.xml");
-
- PersistentClass model = config.getClassMapping("com.mit.test.Person");
- //sessionFactory哪下子,快接纳person爷爷进去
- ((SessionFactoryImpl)factory).addPersistentClass(model, config.getMapping());
- //生成数据库
- SchemaExport export = new SchemaExport(config,((SessionFactoryImpl)factory).getSettings());
- export.execute(true, true,false,true);
- //测试一下,随便给个名字什么的
- PropertyUtils.setProperty(person, "name", "chenzhi");
- Session session = factory.openSession();
- Transaction tran = session.beginTransaction();
- try
- {
- //保存
- session.save(person);
- tran.commit();
- }
- catch (Exception e)
- {
- e.printStackTrace();
- tran.rollback();
- }
- finally
- {
- session.close();
- }
- }
-
- public void tearDown()
- {
- factory.close();
- }
-
-
- }
评论
# re: Hibernate自定义表单完全解决方案(无需重置SessionFactory) 回复 更多评论
2009-10-26 21:28 by
非常棒。你的这种实现为不重启刷新sessionFactory提供了一种非常好的思路。顶你。
|