今天手动实现了一个小例子,模拟Hibernate将关系型数据库中的记录映射为Java对象的过程,虽然不太实用,但是却很简单明了地说明了映射的过程。下图是这个例子的类图:
我们首先对上面的类图做一个简要的说明,IEntity是一个所有的实体都要实现的接口,它的getDefinition方法返回的是这个实体的定义(EntityDefinition),save方法接受一个java.sql.Connection类型的参数,然后把实体自己保存到数据库中,为简单起见这里没有定义其它数据访问的方法。
EntityDefinition描述了一个实体映射到数据库中表的规则,为了简单起见我在这里设置的规则都是很简单的,它的tableName属性直接说明了它所属于的实体对应的数据库中的表名,实体与它本身的定义是一对一的关系,每一个实体都会持有它自己的定义。另外PropertyDefinition描述的是实体中的属性映射到数据库某个表中的字段的规则,EntityDefinition与它是一对多的关系,因为一个实体会有多个属性对应着数据库中表的多个字段。
AbstractEntity是IEntity的默认实现,此类的名字以Abstract开头是为了说明这个类是为了继承而设计的,那么继承它的子类可以得到什么好处呢?因为子类继承自一个父类就和认干爹差不多,若是没有好处谁情愿做人家的干儿子是不是?我们在此承诺,继承自AbstractEntity的子类不用自己编写任何持久化的代码就能实现将自己保存到数据库中的愿望,它所要做的就是像POJO那样声明一些属性,并在自己定义的包中建立一个如下格式的配置文件:类名.hbm.xml,例如对于Customer类来说,要建立一个Customer.hbm.xml的配置文件,文件的内容和上述类图中提供的类似。
先让我们看看IEntity的源码:
package com.neuqsoft.data.analyse.common; import java.sql.Connection; import java.sql.SQLException; public interface IEntity { EntityDefinition getDefinition(); void save(Connection conn)throws SQLException; }
接口中描述的方法都很容易看懂,这里不加赘述,下面是AbstractEntity的源码:
package com.neuqsoft.data.analyse.common; import java.lang.reflect.Field; import java.math.BigDecimal; import java.sql.Connection; import java.sql.Date; import java.sql.PreparedStatement; import java.sql.SQLException; public abstract class AbstractEntity implements IEntity { private EntityDefinition entityDef; protected AbstractEntity(){ entityDef=new EntityDefinition(getClass()); } public final void save(Connection conn)throws SQLException{ deleteIfExist(conn);//如果记录存在先删除 doSave(conn); } private void deleteIfExist(Connection conn)throws SQLException{ PropertyDefinition idDefinition=entityDef.getIdDefinition(); String deleteSQL="delete from "+entityDef.getTableName()+" where "+idDefinition.getColumnName()+"=?"; System.out.println("DELETE:"+deleteSQL); PreparedStatement ps=conn.prepareStatement(deleteSQL); if(ps!=null){ setValue(ps,1,idDefinition); ps.executeUpdate(); ps.close(); } } private void setValue(PreparedStatement ps, int index, PropertyDefinition pd) throws SQLException{ int type=pd.getType(); Object value=getValue(pd.getName()); if(type==PropertyDefinition.TYPE_STRING) ps.setString(index,value.toString()); else if(type==PropertyDefinition.TYPE_NUMBER) ps.setBigDecimal(index,(BigDecimal)value); else if(type==PropertyDefinition.TYPE_DATE) ps.setDate(index,(Date)value); } private Object getValue(String name) { try{ Field f=getClass().getDeclaredField(name); f.setAccessible(true); return f.get(this); }catch (Exception e) { throw new RuntimeException(e); } } private final void doSave(Connection conn)throws SQLException{ PropertyDefinition[] pds=entityDef.getAllPropertyDefinitions(); StringBuffer sb=new StringBuffer("insert into "+entityDef.getTableName()+"("); StringBuffer valueBuffer=new StringBuffer("values("); for(int i=0;i<pds.length;i++){ sb.append(pds[i].getColumnName()); valueBuffer.append("?"); if(i<pds.length-1){ sb.append(","); valueBuffer.append(","); } } String insertSQL=sb.toString()+")"+valueBuffer.toString()+")"; System.out.println("INSERT:"+insertSQL); PreparedStatement ps=conn.prepareStatement(insertSQL); if(ps!=null){ for(int i=0;i<pds.length;i++) setValue(ps,i+1,pds[i]); ps.executeUpdate(); ps.close(); } } public EntityDefinition getDefinition() { return entityDef; } }
为了实现save(Connection conn)方法,AbstractEntity类定义了几个私有方法,在此做简要说明:
(1)deleteIfExist(Connection conn)throws SQLException 如果实体已经在数据库中存在则首先删除,然后保存,相当于更新操作,判断是否存在的依据是实体的ID,如果实体在数据库中不存在此方法相当于零操作。
(2)setValue(PreparedStatement ps, int index, PropertyDefinition pd) 为PreparedStatement设置参数,目前PropertyDefinition所支持的参数只有三种:字符串String类型,日期Date类型以及数字BigDicemal类型。我们当然可以让它支持更多的类型,这里只支持三种类型只是为了简化起见。
(3)Object getValue(String name) 使用反射机制获取实体某一个属性所对应的属性值,name是声明的属性名,例如Customer类中所声明的private String name。
(4)doSave(Connection conn)throws SQLException 真正将实体保存到数据库中的操作。
AbstractEntity类的构造方法虽然只有一行语句:entityDef=new EntityDefinition(getClass());却是整个系统的关键,因为它初始化了实体的定义,下面让我们看看EntityDefinition的源码:
package com.neuqsoft.data.analyse.common; import java.net.URL; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; public class EntityDefinition{ private String tableName;//实体对应的表 public static final String CONFIG_SUFFIX=".hbm.xml"; //<实体属性,实体属性的定义> private Map propertyDefMap=new HashMap(); // public EntityDefinition(Class clazz) { String configFileName=clazz.getSimpleName()+CONFIG_SUFFIX; URL url=clazz.getResource(configFileName); try{ Document document=new SAXReader().read(url.openStream()); Element root=document.getRootElement(); tableName=root.attribute("tableName").getValue(); //mapping property to column of table Iterator propElements=root.elementIterator("Property"); while(propElements.hasNext()){ Element element=(Element) propElements.next(); PropertyDefinition pd=new PropertyDefinition(element); propertyDefMap.put(pd.getName(), pd); } }catch(Exception e){ e.printStackTrace(); throw new RuntimeException(e); } } public PropertyDefinition getIdDefinition(){ Iterator it=propertyDefMap.values().iterator(); while(it.hasNext()){ PropertyDefinition pd=(PropertyDefinition)it.next(); if(pd.isId()) return pd; } return null; } public PropertyDefinition[] getAllPropertyDefinitions(){ Collection cl=propertyDefMap.values(); return (PropertyDefinition[]) cl.toArray(new PropertyDefinition[cl.size()]); } public String getTableName() { return tableName; } }
显而易见,EntityDefinition是通过获取类似Customer.hbm.xml的配置文件来初始化自己的,它的PropertyDefinition getIdDefinition()方法返回其所属于的实体的ID的PropertyDefinition实例,这里除了isId()方法返回true外,和其它属性的定义没有区别。这里假定每一个实体的配置文件都会指定一个ID,如下所示:<Property name="id" columnName="ID" type="STRING" isId="true"/>
可以看出EntityDefinition维护着一个Map,这个Map是由实体的属性名映射到其对应的PropertyDefinition(属性定义)。下面是PropertyDefinition的源码:
package com.neuqsoft.data.analyse.common; import org.dom4j.Element; public class PropertyDefinition{ public static final int TYPE_UNKNOWN=-1; public static final int TYPE_STRING=0; public static final int TYPE_NUMBER=1; public static final int TYPE_DATE=2; private String name;//实体中的属性名 private String columnName;//实体相应属性对应表中的列名 private int type;//columnName在表中定义的类型 private boolean id;//是否是标识符 public PropertyDefinition(String name, String columnName, int type) { this.name = name; this.columnName = columnName; this.type = type; } public PropertyDefinition(String name, String columnName, String type){ this(name,columnName,analyseType(type)); } public PropertyDefinition(Element el){ setName(el.attributeValue("name")); setColumnName(el.attributeValue("columnName")); setType(analyseType(el.attributeValue("type"))); String isIdString=el.attributeValue("isId"); boolean isId=(isIdString==null?false:Boolean.valueOf(isIdString).booleanValue()); setId(isId); } public String getColumnName() { return columnName; } public void setColumnName(String columnName) { this.columnName = columnName; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getType() { return type; } public void setType(int type) { this.type = type; } private static int analyseType(String type) { if("STRING".equalsIgnoreCase(type)) return TYPE_STRING; else if("NUMBER".equalsIgnoreCase(type)) return TYPE_NUMBER; else if("DATE".equalsIgnoreCase(type)) return TYPE_DATE; else return TYPE_UNKNOWN;//Unknown type } public boolean isId() { return id; } public void setId(boolean id) { this.id = id; } }
下面是用于测试的Customer.java以及Customer.hbm.xml的源码:
import java.math.BigDecimal; import com.neuqsoft.data.analyse.common.AbstractEntity; public class Customer extends AbstractEntity { private String id; private String name; private BigDecimal age; public BigDecimal getAge() { return age; } public void setAge(BigDecimal age) { this.age = age; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
<Entity tableName="CUSTOMER"> <Property name="id" columnName="ID" type="STRING" isId="true"/> <Property name="name" columnName="NAME" type="STRING"/> <Property name="age" columnName="AGE" type="NUMBER"/> </Entity>
The End.