Java自定義表單、自定義字段


      最近想實現用戶自定義數據庫中的字段,我想大部分人第一想到的就是EAV(Entity-Attribute-Value),這種方式對於寫一個小的畢業設計應該還可以使用,當然也有很多CMS系統采用這種方式,畢竟其中Value表中的數據會猛增,同樣,會涉及到查詢優化問題,暫不考慮。

     其次,在J2EE中,如果使用spring+hbiernate+springMVC(struts2),Entity類有兩種方式和數據庫進行映射,一種是注解方式,一種是*.hbm.xml配置文件方式。

     ①注解方式,對於注解方式,因為最終目的是根據自定義的字段可以實時的在數據庫表中對其字段進行生成,然后可以使用,最初想到的解決方法是重寫相應實體類的.class文件,然后根據最新的.class文件生成相應的*.hbm.xml文件,利用configuration重新讀取*.hbm.xml文件來建立buildSessionFactory(下面會給出詳細代碼),最后發現即使實現了增加字段,也無法通過這種方式刪除字段,不過還是看看往.class文件中寫入field以及其getter/setter方法的java語句吧。

 1 /* 2 * 添加字段
 3 */
 4 public class AddFieldAdapter extends ClassAdapter {
 5 
 6     private int accessModifier;
 7     private String name;
 8     private String desc;
 9     private boolean isFieldPresent;
10 
11     public AddFieldAdapter(ClassVisitor cv, int accessModifier, String name,
12             String desc) {
13         super(cv);
14         this.accessModifier = accessModifier;
15         this.name = name;
16         this.desc = desc;
17     }
18 
19     public FieldVisitor visitField(int access, String name, String desc,
20             String signature, Object value) {
21         if (name.equals(this.name)) {
22             isFieldPresent = true;
23         }
24         return cv.visitField(access, name, desc, signature, value);
25     }
26 
27     public void visitEnd() {
28         if (!isFieldPresent) {
29             FieldVisitor fv = cv.visitField(accessModifier, name, desc, null,
30                     null);
31 
32             if (null != fv) {
33                 fv.visitEnd();
34             }
35         }
36         cv.visitEnd();
37     }
39 } 
 1             // 創建get,public,無參數,有返回值
 2             MethodVisitor mv = cWriter.visitMethod(Opcodes.ACC_PUBLIC, "get"
 3                     + StringUtils.capitalize(filedName), "()" + type, null,//type為返回的類型
 4                     null);
 5             mv.visitCode();
 6             mv.visitVarInsn(Opcodes.ALOAD, 0);//將this壓棧
 7             mv.visitFieldInsn(Opcodes.GETFIELD,
 8                     this.entityClass.getSimpleName(), filedName, type);
 9             mv.visitInsn(Opcodes.ARETURN);
10             mv.visitMaxs(1, 1);
11             mv.visitEnd();
12 
13             // 創建set方法,public,傳遞一個參數
14             mv = cWriter.visitMethod(Opcodes.ACC_PUBLIC,//方法名為public
15                     "set" + StringUtils.capitalize(filedName), "(" + type //傳遞一個參數
16                             + ")V", null, null);//V表示返回的是void
17             mv.visitCode();//開始執行
18             mv.visitVarInsn(Opcodes.ALOAD, 0);//將this壓棧
19             mv.visitVarInsn(Opcodes.ALOAD, 1);//將局部變量壓棧
20             mv.visitFieldInsn(Opcodes.PUTFIELD,
21                     this.entityClass.getSimpleName(), filedName, type);
22             mv.visitInsn(Opcodes.ARETURN);
23             mv.visitMaxs(2, 2);
24             mv.visitEnd();//執行結束

     關於如何將.class文件生成*.hbm.xml可以從網上找相關模板,借助模板將class文件轉換生成xml配置文件方式,因為重點不采用這種方式,所以簡單介紹下如何重寫class文件即可,關於如何從*.hbm.xml文件重構sessionfactory映射到數據庫中在稍后貼出代碼。

     ②配置文件方式,配置文件的方式對於項目中使用本身就不是特別方便的(相對於注解來說),所有這種方式也是一開始只是為了嘗試在重寫了*.hbm.xml后是否可以通過代碼的方式,在不重啟服務器的情況下,將修改的內容實時的反映到我們的數據庫中,所以在一開始的時候定義model或者entity時候,就應該寫個與之對應的*.hbm.xml,例如下面這樣。

 1 /**
 2 *實體類
 3 */
 4 public class Contact extends CustomizableEntity {
 5 
 6     /**
 7      * ID
 8      */
 9     private int id;
10 
11     /**
12      * 名稱
13      */
14     private String name;
15 
16     public int getId() {
17         return id;
18     }
19 
20     public String getName() {
21         return name;
22     }
23 
24     public void setId(int id) {
25         this.id = id;
26     }
27 
28     public void setName(String name) {
29         this.name = name;
30     }
31 }
 1 <?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
 2 <hibernate-mapping auto-import="true" default-access="property"
 3     default-cascade="none" default-lazy="true">
 4 
 5     <class abstract="false" dynamic-insert="false" dynamic-update="false"
 6         mutable="true" name="com.hfmx.model.Contact" optimistic-lock="version"
 7         polymorphism="implicit" select-before-update="false" table="tb_contact">
 8         <id column="fld_id" name="id">
 9             <generator class="native" />
10         </id>
11 
12         <property column="fld_name" generated="never" lazy="false"
13             name="name" optimistic-lock="true" type="string" unique="false" />
14         <dynamic-component insert="true" name="customProperties"
15             optimistic-lock="true" unique="false" update="true">
16         </dynamic-component>
17     </class>
18 </hibernate-mapping>

     上面的實體類中繼承了一個父類,以及XML文件中的標簽dynamic-component都是為了可以自定義字段做准備的,我們暫且忽略這部分內容,主要看看如何重構sessionFactory,其實這個實現的方式也是從網上找到的,當然為了能夠與hibernate4.0結合,部分地方作了修改,下面把代碼貼出來,重點地方會做點注釋。

 1 public class HibernateUtil {
 2 
 3     private static HibernateUtil instance;
 4 
 5     private Configuration configuration;
 6 
 7     private SessionFactory sessionFactory;
 8 
 9     private Session session;
10 
11     public synchronized static HibernateUtil getInstance() {
12         if (null == instance) {
13             instance = new HibernateUtil();
14         }
15         return instance;
16     }
17 
18     private synchronized SessionFactory getSessionFactory() {
19         if (null == sessionFactory) {
20             Configuration _configuration = this.getConfiguration();
21 
22             ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
23                     .applySettings(_configuration.getProperties())
24                     .buildServiceRegistry();
25 
26             sessionFactory = _configuration
27                     .buildSessionFactory(serviceRegistry);
28         }
29         return sessionFactory;
30     }
31 
32     public synchronized Session getCurrentSession() {
33         if (null == session) {
34             session = getSessionFactory().openSession();
35             session.setFlushMode(FlushMode.COMMIT);    
36     }
37         return session;
38     }
39 
40     private synchronized Configuration getConfiguration() {
41         if (null == configuration) {
42             try {
43                 // 默認加載hibernate.cfg.xml
44                 configuration = new Configuration().configure();
45                 Class entityClass = Contact.class;
46                 String filePath = entityClass.getResource(
47                         entityClass.getSimpleName() + ".hbm.xml").getPath();
48                 // 替換空格
49                 filePath = filePath.replace("%20", " ");
50 
51                 File file = new File(filePath);
52                 // 通過class加載會發現在j2ee中不行
53                 // configuration.addClass(entityClass);
54                 //通過加載classes文件夾下面的文件,獲取實時修改后的XML文件
55                 configuration.addFile(file);
56             } catch (HibernateException e) {
57                 e.printStackTrace();
58             }
59         }
60         return configuration;
61     }
62 
63     /**
64      * 重置
65      */
66     public void reset() {
67         Session session = getCurrentSession();
68         if (null != session) {
69             session.flush();
70             if (session.isOpen()) {
71                 session.close();
72             }
73         }
74         SessionFactory sf = getSessionFactory();
75         if (null != sf) {
76             sf.close();
77         }
78         this.configuration = null;
79         this.sessionFactory = null;
80         this.session = null;
81     }
82 
83     public PersistentClass getClassMapping(Class entityClass) {
84         return getConfiguration().getClassMapping(entityClass.getName());
85     }
86 }

     這種方式即可在修改完XML后立即加載修改后的XML,對其進行映射到數據庫中,然后生成新添加的字段,這種方式存在的弊端也在文章一開始做了介紹,無法刪除字段,同時,配置文件在實際項目中越來越多的被Annotation所替代掉。所以,需要有一種新的方式來實現自定義字段,下面將重點介紹這種方式,同時,下面的內容也是對此配置方式的一個補充。如果看到這里覺得配置文件方式介紹的不夠詳細,可以在下面找出其中很多知識點。

     現在,着重講解通過純的sql來實現數據庫的自定義字段功能….

     首先,我們來看下環境,我用的是springMVC+spring3+hibernate4.0

     同時,對應新增的字段,要通過key-value的方式進行保存,即放在Map集合中,這樣方便后期的讀寫

 1 /**
 2  * 支持自定義字段的業務實體類基類
 3  * 
 4  * @author wy
 5  * 
 6  */
 7 public abstract class CustomizableEntity {
 8 
 9     private Map<String, Object> customProperties;
10 
11     public Map<String, Object> getCustomProperties() {
12         if (null == customProperties)
13             customProperties = new HashMap<String, Object>();
14         return customProperties;
15     }
16 
17     public void setCustomProperties(Map<String, Object> customProperties) {
18         this.customProperties = customProperties;
19     }
20 
21     public Object getValueOfCustomField(String name) {
22         return getCustomProperties().get(name);
23     }
24 
25     public void setValueOfCustomField(String name, Object value) {
26         getCustomProperties().put(name, value);
27     }
28 }
 1 /**
 2 * 實體類MyUser繼承CustomizableEntity,這個類里面已存在字段可視為固定字段
 3 */
 4 @Entity
 5 public class MyUser extends CustomizableEntity {
 6 
 7     /**
 8      * ID
 9      */
10     private int id;
11 
12     /**
13      * 姓名
14      */
15     private String userName;
16 
17     @Id
18     @GeneratedValue
19     public int getId() {
20         return id;
21     }
22 
23     public String getUserName() {
24         return userName;
25     }
26 
27     public void setId(int id) {
28         this.setValueOfCustomField("id", id);
29         this.id = id;
30     }
31 
32     public void setUserName(String userName) {
33         this.setValueOfCustomField("username", userName);
34         this.userName = userName;
35     }
36 }

     完成上面兩個類,在啟動項目后,數據庫中會添加一個myuser表,同時擁有兩個字段(ID,userName)。

     此時,我們來嘗試看,看可否往myuser表中添加一個字段,通過下面簡單的方式。

 1   /**
 2    * 添加字段列(UserDefineField是一個簡單的實體類,包括自定義字段的name,*type以及中文名稱,描述等等)
 3    */
 4    public void addFieldColumn(Class clazz, UserDefineField userDefine) {
 5         try {
 6             Session session = this.sessionFactory.getCurrentSession();
 7 
 8             String sql = "";
 9             //驗證字段是否已經存在
10             sql = "select count(*) as c from userdefinefield where tableName='"
11                     + clazz.getCanonicalName()
12                     + "' and fieldName='"
13                     + userDefine.getFieldName() + "'";
14             Query countQuery = session.createSQLQuery(sql);
15             List<Object> list = countQuery.list();
16             long count = 0;
17             if (list.size() > 0) {
18                 Object object = list.get(0);
19                 if (null != object) {
20                     count = Long.parseLong(object.toString());
21                 }
22             }
23             System.out.println("count:" + count);
24 
25             if (count <= 0) {
26                 // 字段不存在是添加字段
27                 sql = "alter table " + clazz.getSimpleName() + " add column "
28                         + userDefine.getFieldName() + " "
29                         + userDefine.getFieldType();
30                 Query query = session.createSQLQuery(sql);
31                 query.executeUpdate();
32 
33                 // 修改自定義字段表
34                 sql = "insert into userdefinefield (tableName,fieldName,fieldType,fieldCN,fieldDesc) values('"
35                         + clazz.getCanonicalName()
36                         + "','"
37                         + userDefine.getFieldName()
38                         + "','"
39                         + userDefine.getFieldType()
40                         + "','"
41                         + userDefine.getFieldCN()
42                         + "','"
43                         + userDefine.getFieldDesc() + "')";
44                 query = session.createSQLQuery(sql);
45                 query.executeUpdate();
46             }
47         } catch (Exception e) {
48             System.out.println("純數據庫方式動態添加字段名稱失敗");
49             e.printStackTrace();
50         }
51     }

       執行完后我們會發現數據庫表中確實已經增加了一個新的字段,那對於刪除字段,當然也就類似了,簡單的看下代碼

 1    /**
 2     * 刪除字段列
 3     * 
 4     * @param clazz
 5     * @param fieldName
 6     */
 7     public void delFieldColumn(Class clazz, String fieldName) {
 8         Session session = this.sessionFactory.getCurrentSession();
 9         try {
10             // 刪除字段
11             String sql = "alter table " + clazz.getSimpleName()
12                     + " drop column " + fieldName + "";
13             Query query = session.createSQLQuery(sql);
14             query.executeUpdate();
15 
16             sql = "delete from userdefinefield where tableName='"
17                     + clazz.getCanonicalName() + "' and fieldName='"
18                     + fieldName + "'";
19             query = session.createSQLQuery(sql);
20             query.executeUpdate();
21 
22         } catch (Exception e) {
23             System.out.println("純數據庫方式動態刪除字段名稱失敗");
24             e.printStackTrace();
25         }
26     }

      看到這里,很多人會不會想,這樣實現只要會sql的人都可以,主要是如何對數據進行CRUD的操作,是的,光看上面的代碼肯定覺得so easy,接下來看看如果查詢、保存、修改、刪除吧

 ① 查詢

 1    /**
 2     * 根據ID進行用戶查詢
 3     * 
 4     * @param clazz
 5     * @param id
 6     * @return
 7     */
 8     public MyUser searchMyUser(Class clazz, int id) {
 9         MyUser myUser = new MyUser();
10         try {
11             Session session = this.sessionFactory.getCurrentSession();
12             String sql = "SELECT * FROM (select * from myuser where id="
13                     + id
14                     + " ) m join (select * from userdefinefield where tableName='"
15                     + clazz.getCanonicalName() + "') AS define";
16 
17             Query query = session.createSQLQuery(sql).setResultTransformer(
18                     Transformers.ALIAS_TO_ENTITY_MAP);
19             List<Map<String, Object>> list = (List<Map<String, Object>>) query
20                     .list();
21 
22             if (list.size() > 0) {
23                 Map<String, Object> map = list.get(0);
24 
25                 // 固有屬性
26                 myUser.setId(Integer.parseInt(map.get("id").toString()));
27                 myUser.setUserName(map.get("userName").toString());
28             }
29 
30             for (Map<String, Object> map : list) {
31 
32                 // 自定義列名
33                 String fieldName = map.get("fieldName").toString();
34 
35                 myUser.setValueOfCustomField(fieldName, (null == map
36                         .get(fieldName)) ? "" : map.get(fieldName).toString());
37 
38                 UserDefine define = new UserDefine(map);
39                 myUser.setValueOfExctalyCustomProp(fieldName, define);
40             }
41 
42             return myUser;
43         } catch (Exception e) {
44             System.out.println("根據ID進行查詢出現錯誤");
45             e.printStackTrace();
46             return null;
47         }
48     }

  查詢后,在control中打印出來看看效果

 1  // ****查詢信息
 2  MyUser myUser = this.myservice.searchMyUser(MyUser.class, id);
 3 
 4  System.out.println("******查詢結果******");
 5  System.out.println("id:" + myUser.getId());
 6  System.out.println("name:" + myUser.getUserName());
//自定義字段key-value顯示
7 for (String key : myUser.getCustomPropties.keySet()) { 8 System.out.println("" + key + ":"+key+” value:” + myUser.getValueOfCustomField(key)); 10 }

② 保存

 1  /**
 2      * 保存信息
 3      * 
 4      * @param myuser
 5      */
 6     public void saveMyUser(MyUser myuser) {
 7         try {
 8             Session session = this.sessionFactory.getCurrentSession();
 9 
10             String sql = "insert into " + myuser.getClass().getSimpleName()
11                     + " ";
12             int index = 0;
13             for (String key : myuser.getCustomProperties().keySet()) {
14                 if (index == 0) {
15                     sql += "(" + key + "";
16                 } else {
17                     if (index == myuser.getCustomProperties().size() - 1) {
18                         sql += "," + key + ") ";
19                     } else {
20                         sql += "," + key + "";
21                     }
22                 }
23                 index++;
24             }
25 
26             index = 0;
27             for (String key : myuser.getCustomProperties().keySet()) {
28                 if (index == 0) {
29                     sql += "values('" + myuser.getCustomProperties().get(key)
30                             + "'";
31                 } else {
32                     if (index == myuser.getCustomProperties().size() - 1) {
33                         sql += ",'" + myuser.getCustomProperties().get(key)
34                                 + "')";
35                     } else {
36                         sql += ",'" + myuser.getCustomProperties().get(key)
37                                 + "'";
38                     }
39                 }
40                 index++;
41             }
42 
43             System.out.println("保存用戶信息的sql:" + sql);
44             Query query = session.createSQLQuery(sql);
45             query.executeUpdate();
46 
47         } catch (Exception e) {
48             System.out.println("保存用戶信息出錯:" + e.getMessage());
49             e.printStackTrace();
50         }
51     }

③  修改

 1    /**
 2      * 修改信息
 3      * 
 4      * @param myUser
 5      */
 6     public void updateMyUser(MyUser myUser) {
 7         try {
 8             Session session = this.sessionFactory.getCurrentSession();
 9 
10             String sql = "update myuser set userName='" + myUser.getUserName()
11                     + "'";
12             if (myUser.getCustomProperties().size() > 0) {
13                 for (String key : myUser.getCustomProperties().keySet()) {
14                     sql += "," + key + "='"
15                             + myUser.getCustomProperties().get(key) + "'";
16                 }
17             }
18             sql += " where id=" + myUser.getId();
19 
20             System.out.println("修改用戶信息的sql:" + sql);
21 
22             Query query = session.createSQLQuery(sql);
23             query.executeUpdate();
24 
25         } catch (Exception e) {
26             System.out.println("修改用戶信息出錯:" + e.getMessage());
27             e.printStackTrace();
28         }
29     }

④ 刪除

     刪除如果是根據ID刪除,那就一點影響都沒有了,如果是根據動態列內容去刪除,那也就是和保存和修改時候處理方式一樣,這里就不列出來了。(哈哈,感覺重復的代碼有點多了)

 

     綜上所述,初步覺得,如果采用這種方式應該可以對項目本身改動的地方不大,同時,表結構基本上能夠很好的維護。在多表聯合查詢的時候,也同樣沒有太復雜的sql代碼。當然,目前沒有運用到大的系統中,不知道可否在后面會遇到問題。

     如果其他童鞋有其他更好的方法,希望可以一起分享!!!!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM