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 = 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()) {
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 = 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代码。当然,目前没有运用到大的系统中,不知道可否在后面会遇到问题。
如果其他童鞋有其他更好的方法,希望可以一起分享!!!!!!