Java web 摘记-动态表单及动态建表实现原理

Java web 摘录-动态表单及动态建表实现原理
应用场景
项目中往往需要动态的创建一个表单,或者添加一个新的数据模板,这时候因为需要在运行时动态的创建表以及动态的维护表字段甚至表关系 使得普通java解决方案变得困难重重。

实现工具
Hibernate + Spring + Groovy +Freemarker
Hibernate 作用很简单负责创建数据库表这样可以避免我们自己去写复杂的sql和判断。
Spring 作为桥梁起到连接纽带的作用
Groovy 做为动态语言,在项目运行时根据模板创建访问数据库,或者控制层代码
Freamker 可以根据提前定义好的模板生成 hibernate配置文件,以及Groovy代码


实现原理
首先创建Form 和 FromAttribute 两张表关系一对多。
Form表 记录表单的名称,类别,甚至是作为在动态生成表单时的css样式信息
FromAttribute表 记录表单字段信息,如名称,类别等。
有了表单以及表单项的信息后就可以创建数据库表了。


测试代码:
public void testGenerator() {
         Form form = formService.getAll().get(0);
         List<FormAttribute> list = formAttributeService
                 .getAttributeListByFormId(form.getId());
         form.setFormAttributeList(list);
         DbGenerator dg = new DbGenerator(form, dataSource);
         dg.generator();
}
DbGenerator

import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
public class DbGenerator {

     private DataSource dataSource;
     protected Map root = new HashMap();
     private static Logger log = LoggerFactory.getLogger(FormGenerator.class);
     protected String path;
     protected String packageName;
     private Form form;
     protected Configuration getConfig(String resource) {
         Configuration cfg = new Configuration();
         cfg.setDefaultEncoding("UTF-8");
         cfg.setClassForTemplateLoading(this.getClass(), resource);
         return cfg;
     }
     public DbGenerator(Form form ,DataSource dataSource) {
         this.form = form;
         this.dataSource = dataSource;
     }
     public void generator() {
         if(null == form.getFormAttributeList() || form.getFormAttributeList().size() == 0){
             return ;
         }
         Template t;
         try {
             t = getConfig("/template").getTemplate("hibernate.ftl");
             Writer out = new StringWriter();
             t.process(getMapContext(), out);
             String xml = out.toString();
             createTable(xml);
             log.debug(xml);
         } catch (IOException e) {
             e.printStackTrace();
         } catch (TemplateException e) {
             e.printStackTrace();
         }
     }
     @SuppressWarnings("unchecked")
     Map getMapContext() {
         root.put("entity", form);
         return root;
     }
     public void createTable(String xml) {
         org.hibernate.cfg.Configuration conf = new org.hibernate.cfg.Configuration();
         conf.configure("/hibernate/hibernate.cfg.xml");
         Properties extraProperties = new Properties();
         extraProperties.put("hibernate.hbm2ddl.auto", "create");
         conf.addProperties(extraProperties);
         conf.addXML(xml);
         SchemaExport dbExport;
         try {
             dbExport = new SchemaExport(conf, dataSource.getConnection());
             // dbExport.setOutputFile(path);
             dbExport.create(false, true);
         } catch (SQLException e) {
             // TODO Auto-generated catch block
             e.printStackTrace();
         }
     }
}

hibernate.ftl
<?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.name}"
         table="`${entity.tableName}`"
         dynamic-update="false"
         dynamic-insert="false"
         select-before-update="false"
         optimistic-lock="version">
         <id
             name="id"
             column="id"
             type="java.lang.String"
             unsaved-value="null">
             <generator class="uuid" />
         </id>
         <#if entity.formAttributeList?exists>
             <#list entity.formAttributeList as attr>
                 <#if attr.name == "id">
                 <#else>
         <property
             name="${attr.name}"
             type="java.lang.String"
             update="true"
             insert="true"
             access="property"
             column="`${attr.columnName}`"
             length="${attr.length}"
             not-null="false"
             unique="false"
         />

                 </#if>
             </#list>
         </#if>

     </class>
</hibernate-mapping>


hibernate.cfg.xml
<!DOCTYPE hibernate-configuration
     PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
     "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
         <property name="dialect">org.hibernate.dialect.SQLServerDialect</property>
     <property name="connection.driver_class">net.sourceforge.jtds.jdbc.Driver</property>
     <property name="connection.url">jdbc:jtds:sqlserver://127.0.0.1:1433;databasename=struts;SelectMethod=cursor</property>
     <property name="connection.username">sa</property>
     <property name="connection.password">sa</property>

     <property name="show_sql">true</property>
     <property name="hibernate.hbm2ddl.auto">update</property>
<!--
     <mapping resource="hibernate/FormAttribute.hbm.xml" />
     <mapping resource="hibernate/Form.hbm.xml" />
     -->
</session-factory>
</hibernate-configuration>


利用groovy动态创建访问代码
创建好数据库后 就要利用groovy动态创建访问代码了:
先看测试代码 再看具体实现:

public void testGroovy() {
         Form form = formService.get("1");
         List<FormAttribute> list = formAttributeService
                 .getAttributeListByFormId(form.getId());
         form.setFormAttributeList(list);
         FormGenerator fg = new FormGenerator(form);
         String groovycode = fg.generator();
         ClassLoader parent = getClass().getClassLoader();
         GroovyClassLoader loader = new GroovyClassLoader(parent);
         Class groovyClass = loader.parseClass(groovycode);
         GroovyObject groovyObject = null;
         try {
             groovyObject = (GroovyObject) groovyClass.newInstance();
         } catch (InstantiationException e) {
             e.printStackTrace();
         } catch (IllegalAccessException e) {
             e.printStackTrace();
         }
         // map中key为formAttribute中描述该表单字段在数据库中的名称c_columnName
         // 具体情况根据formAttribute而定
         Map map = new HashMap();
         map.put("name", "limq");
         // 调用insert方法插入数据
         int c = (Integer) groovyObject.invokeMethod("insert", map);
         // 调用getAll方法获得所有动态表中的数据
         Object o = groovyObject.invokeMethod("getAll", null);
         List list2 = (List) o;
         Object obj = list2.get(0);
         try {
             String tname = (String) BeanUtils.getDeclaredProperty(obj, "name");
             System.out.println(tname);
         } catch (IllegalAccessException e) {
             e.printStackTrace();
         } catch (NoSuchFieldException e) {
             e.printStackTrace();
         }
         // 调用search方法查询动态表
         List<Map> returnList = (List) groovyObject.invokeMethod("search", map);
         for (Map map2 : returnList) {
             // 同理此处根据FromAttribute而定
             System.out.println(map2.get("id"));
             System.out.println(map2.get("name"));
             System.out.println(map2.get("type"));
         }
     }


创建访问数据库Groovy代码
public class FormGenerator {
     protected  Map root = new HashMap();
     private static Logger log = LoggerFactory.getLogger(FormGenerator.class);
         protected String path ;
         protected String packageName ;
         private Form form ;
         protected Configuration getConfig(String resource) {

              Configuration cfg = new Configuration();
             cfg.setDefaultEncoding("UTF-8");
             cfg.setClassForTemplateLoading(this.getClass(), resource);
             return cfg;
         }

         public FormGenerator(Form form){
             this.form = form;
         }

         public String generator(){
             String returnstr = null;
             Template t;
             try {
                 t = getConfig("/template").getTemplate("FormService.ftl");
                 //Writer out = new OutputStreamWriter(new FileOutputStream(new File(path)),"UTF-8");
                 Writer out = new StringWriter();
                 t.process(getMapContext(), out);
                 returnstr = out.toString();
                 log.debug(returnstr);
             } catch (IOException e) {
                 e.printStackTrace();
             } catch (TemplateException e) {
                 e.printStackTrace();
             }
             return returnstr;
         }

         @SuppressWarnings("unchecked")
         Map getMapContext() {
             root.put("entity", form);
             root.put("insert", SqlHelper.buildInsertStatement(form));
             root.put("update", SqlHelper.buildUpdateStatement(form));

             root.put("insertParameter", SqlHelper.buildInsertparameter(form));
             root.put("updateParameter", SqlHelper.buildUpdateparameter(form));

             root.put("delete", SqlHelper.buildDeleteStatement(form));
             root.put("query",  SqlHelper.buildQueryStatement(form));
             return root;
         }
}


利用 freemarker 生成 Groovy 和 hibernate 相关代码
FormService.ftl
import org.springframework.jdbc.core.RowMapper
import org.springframework.jdbc.core.RowMapperResultSetExtractor
import com.glnpu.sige.core.dao.DataSourceFactory
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
class ${entity.name?cap_first}Dao {
      def insert = '${insert}'
      def delete = '${delete}'
      def update = '${update}'
      def int insert( entity){
         def Object[] params = [${insertParameter}]
         <#assign size = entity.formAttributeList?size/>
         def int[] types=[<#list 1..size+1 as p>Types.VARCHAR,<#rt/></#list>]
         return DataSourceFactory.getJdbcTemplate().update(insert, params, types)
     }
      def int update( entity){
         def Object[] params = [${updateParameter}]
         return DataSourceFactory.getJdbcTemplate().update(update, params)
     }
      def int delete(String entityId){
         def Object[] params =[entityId]
         return DataSourceFactory.getJdbcTemplate().update(delete, params)
     }
     def search(entity){
         ${query}
         println(query);
         return DataSourceFactory.getJdbcTemplate().queryForList(query);

     }

}

了解以上的原理后就可以方便的在运行时利用freemarker生成表示层页面以及代码来进行展示。