最近想实现用户自定义数据库中的字段,我想大部分人第一想到的就是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 /* * 添加字段
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,例如下面这样。

 /**
*实体类
*/
public class Contact extends CustomizableEntity { /**
* ID
*/
private int id; /**
* 名称
*/
private String name; public int getId() {
return id;
} public String getName() {
return name;
} public void setId(int id) {
this.id = id;
} public void setName(String name) {
this.name = name;
}
}
 <?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">
<hibernate-mapping auto-import="true" default-access="property"
default-cascade="none" default-lazy="true"> <class abstract="false" dynamic-insert="false" dynamic-update="false"
mutable="true" name="com.hfmx.model.Contact" optimistic-lock="version"
polymorphism="implicit" select-before-update="false" table="tb_contact">
<id column="fld_id" name="id">
<generator class="native" />
</id> <property column="fld_name" generated="never" lazy="false"
name="name" optimistic-lock="true" type="string" unique="false" />
<dynamic-component insert="true" name="customProperties"
optimistic-lock="true" unique="false" update="true">
</dynamic-component>
</class>
</hibernate-mapping>

上面的实体类中继承了一个父类,以及XML文件中的标签dynamic-component都是为了可以自定义字段做准备的,我们暂且忽略这部分内容,主要看看如何重构sessionFactory,其实这个实现的方式也是从网上找到的,当然为了能够与hibernate4.0结合,部分地方作了修改,下面把代码贴出来,重点地方会做点注释。

 public class HibernateUtil {

     private static HibernateUtil instance;

     private Configuration configuration;

     private SessionFactory sessionFactory;

     private Session session;

     public synchronized static HibernateUtil getInstance() {
if (null == instance) {
instance = new HibernateUtil();
}
return instance;
} private synchronized SessionFactory getSessionFactory() {
if (null == sessionFactory) {
Configuration _configuration = this.getConfiguration(); ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
.applySettings(_configuration.getProperties())
.buildServiceRegistry(); sessionFactory = _configuration
.buildSessionFactory(serviceRegistry);
}
return sessionFactory;
} public synchronized Session getCurrentSession() {
if (null == session) {
session = getSessionFactory().openSession();
session.setFlushMode(FlushMode.COMMIT);
}
return session;
} private synchronized Configuration getConfiguration() {
if (null == configuration) {
try {
// 默认加载hibernate.cfg.xml
configuration = new Configuration().configure();
Class entityClass = Contact.class;
String filePath = entityClass.getResource(
entityClass.getSimpleName() + ".hbm.xml").getPath();
// 替换空格
filePath = filePath.replace("%20", " "); File file = new File(filePath);
// 通过class加载会发现在j2ee中不行
// configuration.addClass(entityClass);
//通过加载classes文件夹下面的文件,获取实时修改后的XML文件
configuration.addFile(file);
} catch (HibernateException e) {
e.printStackTrace();
}
}
return configuration;
} /**
* 重置
*/
public void reset() {
Session session = getCurrentSession();
if (null != session) {
session.flush();
if (session.isOpen()) {
session.close();
}
}
SessionFactory sf = getSessionFactory();
if (null != sf) {
sf.close();
}
this.configuration = null;
this.sessionFactory = null;
this.session = null;
} public PersistentClass getClassMapping(Class entityClass) {
return getConfiguration().getClassMapping(entityClass.getName());
}
}

这种方式即可在修改完XML后立即加载修改后的XML,对其进行映射到数据库中,然后生成新添加的字段,这种方式存在的弊端也在文章一开始做了介绍,无法删除字段,同时,配置文件在实际项目中越来越多的被Annotation所替代掉。所以,需要有一种新的方式来实现自定义字段,下面将重点介绍这种方式,同时,下面的内容也是对此配置方式的一个补充。如果看到这里觉得配置文件方式介绍的不够详细,可以在下面找出其中很多知识点。

现在,着重讲解通过纯的sql来实现数据库的自定义字段功能….

首先,我们来看下环境,我用的是springMVC+spring3+hibernate4.0

同时,对应新增的字段,要通过key-value的方式进行保存,即放在Map集合中,这样方便后期的读写

 /**
* 支持自定义字段的业务实体类基类
*
* @author wy
*
*/
public abstract class CustomizableEntity { private Map<String, Object> customProperties; public Map<String, Object> getCustomProperties() {
if (null == customProperties)
customProperties = new HashMap<String, Object>();
return customProperties;
} public void setCustomProperties(Map<String, Object> customProperties) {
this.customProperties = customProperties;
} public Object getValueOfCustomField(String name) {
return getCustomProperties().get(name);
} public void setValueOfCustomField(String name, Object value) {
getCustomProperties().put(name, value);
}
}
 /**
* 实体类MyUser继承CustomizableEntity,这个类里面已存在字段可视为固定字段
*/
@Entity
public class MyUser extends CustomizableEntity { /**
* ID
*/
private int id; /**
* 姓名
*/
private String userName; @Id
@GeneratedValue
public int getId() {
return id;
} public String getUserName() {
return userName;
} public void setId(int id) {
this.setValueOfCustomField("id", id);
this.id = id;
} public void setUserName(String userName) {
this.setValueOfCustomField("username", userName);
this.userName = userName;
}
}

完成上面两个类,在启动项目后,数据库中会添加一个myuser表,同时拥有两个字段(ID,userName)。

此时,我们来尝试看,看可否往myuser表中添加一个字段,通过下面简单的方式。

   /**
* 添加字段列(UserDefineField是一个简单的实体类,包括自定义字段的name,*type以及中文名称,描述等等)
*/
public void addFieldColumn(Class clazz, UserDefineField userDefine) {
try {
Session session = this.sessionFactory.getCurrentSession(); String sql = "";
//验证字段是否已经存在
sql = "select count(*) as c from userdefinefield where tableName='"
+ clazz.getCanonicalName()
+ "' and fieldName='"
+ userDefine.getFieldName() + "'";
Query countQuery = session.createSQLQuery(sql);
List<Object> list = countQuery.list();
long count = 0;
if (list.size() > 0) {
Object object = list.get(0);
if (null != object) {
count = Long.parseLong(object.toString());
}
}
System.out.println("count:" + count); if (count <= 0) {
// 字段不存在是添加字段
sql = "alter table " + clazz.getSimpleName() + " add column "
+ userDefine.getFieldName() + " "
+ userDefine.getFieldType();
Query query = session.createSQLQuery(sql);
query.executeUpdate(); // 修改自定义字段表
sql = "insert into userdefinefield (tableName,fieldName,fieldType,fieldCN,fieldDesc) values('"
+ clazz.getCanonicalName()
+ "','"
+ userDefine.getFieldName()
+ "','"
+ userDefine.getFieldType()
+ "','"
+ userDefine.getFieldCN()
+ "','"
+ userDefine.getFieldDesc() + "')";
query = session.createSQLQuery(sql);
query.executeUpdate();
}
} catch (Exception e) {
System.out.println("纯数据库方式动态添加字段名称失败");
e.printStackTrace();
}
}

执行完后我们会发现数据库表中确实已经增加了一个新的字段,那对于删除字段,当然也就类似了,简单的看下代码

    /**
* 删除字段列
*
* @param clazz
* @param fieldName
*/
public void delFieldColumn(Class clazz, String fieldName) {
Session session = this.sessionFactory.getCurrentSession();
try {
// 删除字段
String sql = "alter table " + clazz.getSimpleName()
+ " drop column " + fieldName + "";
Query query = session.createSQLQuery(sql);
query.executeUpdate(); sql = "delete from userdefinefield where tableName='"
+ clazz.getCanonicalName() + "' and fieldName='"
+ fieldName + "'";
query = session.createSQLQuery(sql);
query.executeUpdate(); } catch (Exception e) {
System.out.println("纯数据库方式动态删除字段名称失败");
e.printStackTrace();
}
}

看到这里,很多人会不会想,这样实现只要会sql的人都可以,主要是如何对数据进行CRUD的操作,是的,光看上面的代码肯定觉得so easy,接下来看看如果查询、保存、修改、删除吧

① 查询

 1    /**
* 根据ID进行用户查询
*
* @param clazz
* @param id
* @return
*/
public MyUser searchMyUser(Class clazz, int id) {
MyUser myUser = new MyUser();
try {
Session session = this.sessionFactory.getCurrentSession();
String sql = "SELECT * FROM (select * from myuser where id="
+ id
+ " ) m join (select * from userdefinefield where tableName='"
+ clazz.getCanonicalName() + "') AS define"; Query query = session.createSQLQuery(sql).setResultTransformer(
Transformers.ALIAS_TO_ENTITY_MAP);
List<Map<String, Object>> list = (List<Map<String, Object>>) query
.list(); if (list.size() > 0) {
Map<String, Object> map = list.get(0); // 固有属性
myUser.setId(Integer.parseInt(map.get("id").toString()));
myUser.setUserName(map.get("userName").toString());
} for (Map<String, Object> map : list) { // 自定义列名
String fieldName = map.get("fieldName").toString(); myUser.setValueOfCustomField(fieldName, (null == map
.get(fieldName)) ? "" : map.get(fieldName).toString()); UserDefine define = new UserDefine(map);
myUser.setValueOfExctalyCustomProp(fieldName, define);
} return myUser;
} catch (Exception e) {
System.out.println("根据ID进行查询出现错误");
e.printStackTrace();
return null;
}
}

查询后,在control中打印出来看看效果

 1  // ****查询信息
MyUser myUser = this.myservice.searchMyUser(MyUser.class, id); System.out.println("******查询结果******");
System.out.println("id:" + myUser.getId());
System.out.println("name:" + myUser.getUserName());
//自定义字段key-value显示
for (String key : myUser.getCustomPropties.keySet()) {
System.out.println("" + key + ":"+key+” value:” + myUser.getValueOfCustomField(key));
}

② 保存

  /**
* 保存信息
*
* @param myuser
*/
public void saveMyUser(MyUser myuser) {
try {
Session session = this.sessionFactory.getCurrentSession(); String sql = "insert into " + myuser.getClass().getSimpleName()
+ " ";
int index = 0;
for (String key : myuser.getCustomProperties().keySet()) {
if (index == 0) {
sql += "(" + key + "";
} else {
if (index == myuser.getCustomProperties().size() - 1) {
sql += "," + key + ") ";
} else {
sql += "," + key + "";
}
}
index++;
} index = 0;
for (String key : myuser.getCustomProperties().keySet()) {
if (index == 0) {
sql += "values('" + myuser.getCustomProperties().get(key)
+ "'";
} else {
if (index == myuser.getCustomProperties().size() - 1) {
sql += ",'" + myuser.getCustomProperties().get(key)
+ "')";
} else {
sql += ",'" + myuser.getCustomProperties().get(key)
+ "'";
}
}
index++;
} System.out.println("保存用户信息的sql:" + sql);
Query query = session.createSQLQuery(sql);
query.executeUpdate(); } catch (Exception e) {
System.out.println("保存用户信息出错:" + e.getMessage());
e.printStackTrace();
}
}

③  修改

    /**
* 修改信息
*
* @param myUser
*/
public void updateMyUser(MyUser myUser) {
try {
Session session = this.sessionFactory.getCurrentSession(); String sql = "update myuser set userName='" + myUser.getUserName()
+ "'";
if (myUser.getCustomProperties().size() > 0) {
for (String key : myUser.getCustomProperties().keySet()) {
sql += "," + key + "='"
+ myUser.getCustomProperties().get(key) + "'";
}
}
sql += " where id=" + myUser.getId(); System.out.println("修改用户信息的sql:" + sql); Query query = session.createSQLQuery(sql);
query.executeUpdate(); } catch (Exception e) {
System.out.println("修改用户信息出错:" + e.getMessage());
e.printStackTrace();
}
}

④ 删除

删除如果是根据ID删除,那就一点影响都没有了,如果是根据动态列内容去删除,那也就是和保存和修改时候处理方式一样,这里就不列出来了。(哈哈,感觉重复的代码有点多了)

综上所述,初步觉得,如果采用这种方式应该可以对项目本身改动的地方不大,同时,表结构基本上能够很好的维护。在多表联合查询的时候,也同样没有太复杂的sql代码。当然,目前没有运用到大的系统中,不知道可否在后面会遇到问题。

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

Java自定义表单、自定义字段的更多相关文章

  1. espcms自定义表单邮件字段

    /include/inc_replace_mailtemplates.php中增加一行就可以了. 如:$replacemail['mailform'][] = array(name => '职位 ...

  2. dedecms(织梦)自定义表单后台显示不全 自定义模型当中添加自定义字段后在后台添加内容后不显示解决方案

    我们常用dedecms 自定义表单做留言功能.但是偶尔会遇到这样一个问题,就是 在前台提交表单后..后天显示不全.特别是中文字符  都不会显示, 比如下图: 这是因为  如果你织梦是gbk的话那就对了 ...

  3. activiti自定义流程之自定义表单(二):创建表单

    注:环境配置:activiti自定义流程之自定义表单(一):环境配置 在上一节自定义表单环境搭建好以后,我就正式开始尝试自己创建表单,在后台的处理就比较常规,主要是针对ueditor插件的功能在前端进 ...

  4. activiti自定义流程之自定义表单(一):环境配置

    先补充说一下自定义流程整个的思路,自定义流程的目的就是为了让一套代码解决多种业务流程,比如请假单.报销单.采购单.协作单等等,用户自己来设计流程图. 这里要涉及到这样几个基本问题,一是不同的业务需求, ...

  5. SpringBoot集成Spring Security(4)——自定义表单登录

    通过前面三篇文章,你应该大致了解了 Spring Security 的流程.你应该发现了,真正的 login 请求是由 Spring Security 帮我们处理的,那么我们如何实现自定义表单登录呢, ...

  6. 【.net+jquery】绘制自定义表单(含源码)

    前言 两年前在力控的时候就想做一个类似的功能,当时思路大家都讨论好了,诸多原因最终还是夭折了.没想到两年多后再这有重新提出要写一个绘制表单的功能.对此也是有点小激动呢?总共用时8.5天的时间基本功能也 ...

  7. activiti自定义流程之整合(三):整合自定义表单创建模型

    本来在创建了表单之后应该是表单列表和预览功能,但是我看了看整合的代码,和之前没有用angularjs的基本没有什么变化,一些极小的变动也只是基于angularjs的语法,因此完全可以参考之前说些的表单 ...

  8. Orchard创建自定义表单

    本文链接:http://www.cnblogs.com/souther/p/4520130.html 主目录 自定义表单模块可以用来获取网站前台用户的信息.自定义表单需要与一个内容类型结合使用.它可以 ...

  9. 用dedecms自定义表单创建简易自助预约系统

    建站往往需要根据客户的需求来增加相应的功能,比如预约.平时用比较多的是织梦系统,那么如何用dedecms自定义表单创建简易自助预约系统呢? 进入dedecms后台,左侧菜单中依次点击“核心” - 频道 ...

  10. 基于vue2.0前端组件库element中 el-form表单 自定义验证填坑

    eleme写的基于vue2.0的前端组件库: http://element.eleme.io 我在平时使用过程中,遇到的问题. 自定义表单验证出坑: 1: validate/resetFields 未 ...

随机推荐

  1. CountDownLatch如何使用

    正如每个Java文档所描述的那样,CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行.在Java并发中,countdownlatch的概念是一 ...

  2. C# windows form如何隐藏窗口?

    you can use this line of code. It wont hide it, but it will be minimized: this.WindowState = FormWin ...

  3. 锋利的js之验证身份证号

    我们在做互联网网站时,注册个人资料时,经常要用到身份证号,我们需要对身份证进验证,不然别人随便输个号码就通过,让你感觉这个网站做得很shit. 身份证号是有规则的. 结构和形式 1.号码的结构  公民 ...

  4. {Reship}{原文}{资治通鉴}

    this article came from here ================================================= 资治通鉴 (361人评分) 9.0   作者 ...

  5. 设计winform自带动态加载工具按钮和实现热键响应

    1.初衷 主要是想设计一个自带添加工具按钮和按钮的快捷键的基窗体.这样以后所设计的窗体只要继承自这个窗体就可以实现热键响应和动态加工具按钮的功能了 写这边文章主要是为了以后使用的时候有个参考,因为这只 ...

  6. [转] --- Error: “A field or property with the name was not found on the selected data source” get only on server

    Error: “A field or property with the name was not found on the selected data source” get only on ser ...

  7. 用Docker Compose启动Nginx和Web等多个镜像

    安装docker-compose 运行命令 curl -L "https://github.com/docker/compose/releases/download/1.9.0/docker ...

  8. Linux课程实践二:编译模块实现内核数据操控

    一.内核模块原理 1. Linux内核增加功能 Linux内核整体结构很庞大,包含了很多的组件,现在有两种方法将需要的功能包含进内核当中: - 静态加载:将所有的功能都编译进Linux内核. - 动态 ...

  9. php的进制转换

    学习了php的进制转换,有很多的知识点,逻辑,也有最原始的笔算,但是我们还是习惯使用代码来实现进制的转换,进制的转换代码有如下:二进制(bin)八进制( oct)十进制( dec)十六进制( hex) ...

  10. [转载] Android Bander设计与实现 - 设计篇

    本文转载自: http://blog.csdn.net/chenxiancool/article/details/17454593 摘要 Binder是Android系统进程间通信(IPC)方式之一. ...