2010年8月31日星期二

用jdbc也能玩出Hibernate的感觉

用jdbc也能玩出Hibernate的感觉: "近期完成一个项目,持久层依旧采用jdbc,因为公司的技术比较落后,项目也不大

采用框架的话,其他同事的要花费时间来学习,成本比较大,但jdbc大量的重复代码,有的时候真的崩溃

apache的开源框架db_util 也用过,感觉不是很顺手



想想算了,自己封装一层,简单的以操作对象的形式完成数据库的调用,这样会节省开发时间



下面说一下基本思路



1.定义简单的配置文件 jdbc.hbm.xml,里面仅仅给出每一个实体类中主键的生成方式,自增的还是手动配置的




<?xml version="1.0" encoding="UTF-8"?>
<jdbc-mapping>
<!-- 定义实体类路径 -->
<class name="com.uniits.privilege.dao.entity.GroupEntity" >
<!-- 实体中对应表中的主键,及数据类型 -->
<id name="groupId" type="int">
<!-- 主键生成方式 ,其中assigned 为手动配置,identity为数据库自增长 -->
<generator class="identity" />
</id>
</class>
<class name="com.uniits.privilege.dao.entity.UserEntity" >
<id name="userId" type="int">
<generator class="identity" />
</id>
</class>
<class name="com.uniits.privilege.dao.entity.FunctionEntity" >
<id name="functionId" type="int">
<generator class="identity" />
</id>
</class>
</jdbc-mapping>




2.在项目启动时,加载所有的实体类并与配置文件jdbc.hbm.xml进行比对,判断jdbc.hbm.xml中定义的 类、属性、类型在每一个实体类中是否存在




private static final Log log = LogFactory.getLog(ValidConfig.class);

private static final String ELEMENT_NODE_CLASS = "class";

private static final String ELEMENT_NODE_ID = "id";

private static final String ELEMENT_ATTR_NAME = "name";

private static final String ELEMENT_ATTR_PRIMITIVE = "type";

private static final String ELEMENT_NODE_GENERATOR = "generator";

private static final String ELEMENT_ATTR_GENERATOR = "class";

private ValidConfig(){

}

/**
* 加在jdbc.hbm.xml配置文件并解析
*
* @throws AppException
*/
public static void loadXml(String path) throws AppException {
// 加在XML所在路径
XMLFactory factory = XMLFactory.getInstance(path);
// 获取根元素下的子节点
Element rootElement = factory.getRootElement();
// 获取节点集合
List<Element> eles = rootElement.elements(ELEMENT_NODE_CLASS);
try {
iteratorNodes(eles);
} catch (Exception e) {
throw new AppException(e);
}
}

/**
* 遍历所有节点
* @param elements
* @throws Exception
*/
public static void iteratorNodes(List<Element> elements) throws Exception{
if(elements.isEmpty()){
return ;
}
for (Element ele : elements) {
String clazz = ele.attributeValue(ELEMENT_ATTR_NAME);
Element eleId = ele.element(ELEMENT_NODE_ID);
String pk = eleId.attributeValue(ELEMENT_ATTR_NAME);
String type = eleId.attributeValue(ELEMENT_ATTR_PRIMITIVE);
try {
// 验证节点信息
Object clazzInstance = validInfo(clazz, pk, type);

// 遍历到element的generator节点并获取其指定的主键生成方式
Element generator = eleId.element(ELEMENT_NODE_GENERATOR);
String generator_class = generator.attributeValue(ELEMENT_ATTR_GENERATOR);
XMLElementEntity entity = new XMLElementEntity();
entity.setClazzName(clazzInstance);
entity.setGenerator(generator_class);
entity.setPkName(pk);
entity.setPrimitive(type);
// 以类名称做为KEY ,保存对象信息
Constants.entityInfo.put(clazzInstance, entity);
} catch (Exception e) {
throw new AppException(e);
}
}
}

/**
* 验证配置文件信息是否正确
* 定义的类、属性及数据类型是否正确
* @param clazz
* @param primaryKey
* @param type
* @return
* @throws Exception
*/
public static Object validInfo(String clazz, String primaryKey, Object type) throws Exception{
try {
Object clazzInstance = Class.forName(clazz).newInstance();
// valid class Fields
if (!validField(clazzInstance, primaryKey, type)) {
throw new AppException("[jdbc.hbm.xml] can't cast fieldType with "+ primaryKey +" "+ type + " in "+ clazz);
}
// split("@") :class@十进制哈希值,取类名
return clazzInstance.toString().split("@")[0];
} catch (ClassNotFoundException e) {
throw new AppException("[jdbc.hbm.xml] can't find class -- "+ clazz);
}
}

/**
* 验证属性
* 比对类中的元素所对应的数据类型是否与jdbc.hbm.xml配置文件中所对应的数据类型一致
* @param clazz
* @param primaryKey
* @param type
* @return
* @throws Exception
*/
public static boolean validField(Object clazz, String primaryKey, Object primitivType) throws Exception{
try {
// 在当前指定类中查找相应属性 【以下必须传一个已经实例化的对象,才能查询类中的所有 属性】
Field field = clazz.getClass().getDeclaredField(primaryKey);
// 获取元素数据类型
String fieldType = MethodUtil.changeWrapClass(field.getType());
if (primitivType.equals(fieldType)) {
return true;
}
} catch (NoSuchFieldException e) {
throw new AppException("[jdbc.hbm.xml] can't find field "+ primaryKey +" in "+ clazz);
}
return false;
}




3.封装baseDAO,进行统一操作,此处就摘取部分操作对象的代码



当需要保存一个实体对象的时




public int executeUpdate(String sql, Object entity) throws Exception{
// 验证基本数据类型
if (MethodUtil.isWrapClass(entity.getClass())) {
return executeUpdate(sql, new Object[] { entity });
}
int result = 0;
pstm = null;
try {
this.conn = ConnectionManager.getInstance().getConnection();
pstm = getPreparedStatementViaEntity(this.conn, sql, entity);
result = pstm.executeUpdate();
} catch (Exception e) {
throw e;
} finally {
closeConnection();
}
return result;
}




这里首先会判断参数entity是否是一个基本数据类型 还是常用的数据类型,比如String ,Date等

当传递参数的时候可能只有一个参数,比如id,而这个id可能是基本数据类型,也可能是常用数据类型所以要在这做一个判断,如果不是常用数据类型则当实体类



当entity是一个常用数据类型的时候,调用另一个方法


public int executeUpdate(String sql, Object[] params) throws Exception {
int result = 0;
try {
conn = ConnectionManager.getInstance().getConnection();
pstm = getPreparedStatementViaParams(conn, sql, params);
result = pstm.executeUpdate();
} catch (Exception e) {
throw e;
} finally {
closeConnection();
}
return result;
}


这里就直接对sql语句中带有的?进行了赋值操作




pstm = getPreparedStatementViaEntity(this.conn, sql, entity);


这一步获取PrepareStatement对象,通过名字可以看出此处要操作的对象是实体类,而不是普通的参数数组




public PreparedStatement getPreparedStatementViaEntity(Connection conn, String sql, Object entity) throws Exception {
if (null != conn) {
this.conn = conn;
}
pstm = conn.prepareStatement(sql);
List<Object> params = ReflectUtil.reflectEntity(entity);
int size = params.size();
for (int i = 0; i < size; i++) {
pstm.setObject(i + 1, params.get(i));
}
return pstm;
}




代码中ReflectUtil.reflectEntity(entity)是反射一个实体类,主要是获取类中的属性,进行赋值操作



把ReflectUtil的完整代码贴出来




/**
* 运用反射机制获取实体类中所对应的属性信息
*
* @param entity [in] 属性信息
* @return
* @throws Exception
*/
public static List<Object> reflectEntity(Object entity) throws Exception{
// 获得实体类的所有属性
Field[] fields = entity.getClass().getDeclaredFields();
// 获取当天类对应的配置文件实体信息
XMLElementEntity clazz = getClassName(entity);
// 获取当前类主键
String pk = clazz.getPkName();
// 获取主键生成方式
String generator = clazz.getGenerator();

List<Object> params = null;
try {
params = iteratorFields(fields, entity, pk, generator);
} catch (Exception e) {
throw e;
}

return params;
}

public static Method getMethod(Object entity, Object parameterType, String fieldName) throws NoSuchMethodException {
Method method = null;
String methodName = null;
if ("boolean".equals(parameterType.toString())) {
methodName = getMethodName(fieldName, Constants.BOOLEAN_METHOD);
} else {
methodName = getMethodName(fieldName, Constants.GET_METHOD);
}
try {
method = entity.getClass().getMethod(methodName);
} catch (SecurityException e) {
throw e;
} catch (NoSuchMethodException e) {
throw e;
}
return method;
}

/**
* 根据类型获取方法名
* 注释: set为setXX()方法、get为getXX()方法、is则因为基本数据类型为boolean的时候,isXX()代替getXX()
*
* @param fileName 名称
* @param type 获取方法类型(set or get or is)
* @return
*/
public static String getMethodName(final String fileName, String type){
return type + fileName.substring(0, 1).toUpperCase() + fileName.substring(1);
}

/**
* 匹配类信息,并返回
*/
public static XMLElementEntity getClassName(Object entity){
Iterator<Entry<Object, XMLElementEntity>> iter = Constants.entityInfo.entrySet().iterator();
// 获取className;【entity = com.uniits.privilege.dao.entity.GroupEntity@c47498】 类
String entityName = entity.toString().split("@")[0];
while(iter.hasNext()){
Entry<Object, XMLElementEntity> entry = iter.next();
String className = entry.getKey().toString();
if(entityName.equals(className)){
return entry.getValue();
}
}
return null;
}
/**
* 遍历实体信息
* @param fields
* @param entity
* @param pk
* @param generator
* @return
* @throws Exception
*/
public static List<Object> iteratorFields(Field[] fields, Object entity, String pk, String generator) throws Exception{
Method method = null;
List<Object> params = new ArrayList<Object>();
for (Field field : fields) {
// 获取实体类单一属性
String fieldName = field.getName();
// 如果当前实体主键生成方式为自增长则获取下一个属性信息
if (fieldName.equals(pk) && generator.equals(GENERATOR_IDENTITY)) {
continue;
}
// 获取属性类型
Class<? extends Object> methodType = field.getType();
try {
method = getMethod(entity, methodType, fieldName);
// 获取当前属性的值
Object value = (Object) method.invoke(entity);
params.add(value);
} catch (SecurityException e) {
throw e;
} catch (IllegalArgumentException e) {
throw e;
} catch (IllegalAccessException e) {
throw e;
} catch (InvocationTargetException e) {
throw e;
} catch (Exception e) {
throw e;
}
}
return params;
}




因为有一个主键自增的问题,所以在代码中有一处是判断当前操作的实体的主键是以什么方式操作的

如果是自增长的,就会在SQL中减少一个参数的赋值

比如 INSERT INTO USER(userId,username) VALUES(seq_userId.nextval,?)

在普通赋值操作的时候 pstm.setString(1,XX);就仅仅做一个赋值操作 所以要排除主键自增的问题



在前面说到,项目加载的时候会把主键生成方式配置咋jdbc.hbm.xml文件中,把实体类中的主键增长方式保存到一个Map



最终的操作只需要2步,Spring+Hibernate结合的时候,需要继承HibernateDaoSupport

然后调用相应的save、delete、update等等方法,传入指定的SQL语句



而我这个也同样如此

加入操作实体


public void save(UserEntity entity){
String sql = 'INSERT INTO t_user(userId,username) VALUES(seq_userId.nextVal,?)';
manager.executeUpdate(sql,entity);
}


只需要这2步,即可保存当前实体信息



更改或者删除信息同样


public void save(XXXXX){
String sql = '';
manager.executeUpdate(sql,XXXX);
}


只需要传递一个SQL及参数即可,参数可以是数组也可以是一个单独的变量



如果是单个参数


public void queryByUserId(int userId){
String sql = 'SELECT username FROM t_user WHERE userId = ?';
ResultSet rs = manager.executeUpdate(sql,userId);
}


皆可查处结果集,做相应操作



如果是多个参数


public void queryByUserId(int userId,String username){
String sql = 'SELECT * FROM t_user WHERE userId = ? and username = ?';
Object[] paramValues = {userId, username};
ResultSet rs = manager.executeUpdate(sql,paramValues);
}


即可查处结果集,做相应操作



这是我现在项目中应用的,总体来说是省了不少事情



但是也有很多不足之处,比如在事物的时候,我的操作都在executeUpdate这个方法中关闭了连接,所以在有事物操作的时候,又显的很棘手,批量操作这也还没有封装,总之只是方便了普通的操作使用,很多地方都需要优化



因为还没看过Hibernate的源码,很多地方都不知道怎么处理,这是在操作对象这用反反射机制做了一下



写的有点乱,好久不总结东西了,希望大家给出好的建议及修改方案……



并没有重复造轮子啊,是公司真的不用框架什么的,也没有同事会,所以始终都不采取框架来进行开发,就封装了这么个东西



晚些时候会把整体封装代码放上来








作者: 分离的北极熊


声明: 本文系JavaEye网站发布的原创文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!




已有 2 人发表回复,猛击->>这里<<-参与讨论





JavaEye推荐






"

没有评论:

发表评论