`
zhb870815
  • 浏览: 25006 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

dbUtils从使用到源码解析

阅读更多

最近工作关系需要对dbutils进行一些了解,来完善公司的测试DAO的框架封装。给本屌丝最大的感慨 麻雀虽小,五脏俱全。

dbutils是Apache组织开发的一个简单,小巧,又具有大部分功能的操作数据库的java组件,dbutis包含三个包,10几个类,够简单的吧!

使用dbutils也非常的简单,只需要初始化QueryRunner类,传入SQL语句,就可以进行增删改查的所有的操作,当然dbutils对查询也做了一些比较简单的查询的封装,主要是对ResultHandle做了一些处理。最终还是通过QueryRunner来进行的操作的。

 

QueryRunner可以直接new进行实例化,也有传入dataSource进行实例化的构造器,前者进行实例化之后,操作时需要传递Connnection参数,方便比较容易获得Connection的应用使用,而使用DataSource进行实例化就可以直接进行数据库操作了。这里特别的提醒一下。如果应用中存在多个数据库源,并且使用了proxool的数据库连接池,那必须设置数据库连接池的别名alias,这是因为数据库连接池在获取连接的时候首先通过别名来获得链接,如果都没有设置别名,将有可能获得错误的数据库连接,后果就严重了!大家可以看看这段代码就明了了。

 

 ConnectionPool cp = null;
        try {
            if (!ConnectionPoolManager.getInstance().isPoolExists(alias)) {
                registerPool();
            }
            cp = ConnectionPoolManager.getInstance().getConnectionPool(alias);
            return cp.getConnection();
        } catch (ProxoolException e) {
            LOG.error("Problem getting connection", e);
            throw new SQLException(e.toString());
        }

 

    如果应用中已经集成了spring,那将dbutils集成进来就非常的简单了,直接通过spring对queryRunner进行管理,这里就不细说了!会用spring的人都知道该怎么做了。至于增删改这些东西都直接通过SQL语句,比较直观,再封装也省不了多少工作量,所以今天主要介绍一下关于将resultSet转化成javaBean方面的转换。

 

实际上,dbutils对一些简单的resultSet-->Bean是有做一个处理器的,主要通过BeanProcessor来对resultSet到Bean的转换,如果不涉及到一些诸如Calendar,Enum,自定义特殊的类型或者说不涉及到一些关联的话,简单的javaBean是可以满足需求的。代码也不复杂:

 

BeanProcessor beanProcessor = new BeanProcessor();
		RowProcessor rowProcessor = new BasicRowProcessor(beanProcessor);
		BeanHandler<T> handle = new BeanHandler<T>(beanClass, rowProcessor);
//进行查询
T t = queryRunner.query(sql, handle);
 

这几行代码应该比较容易看懂了,很简单的几行代码就可以进行Bean的转换了,除了bean的转化,还有toBeanList,toBeanMap的转换,这些都可以轻松的做到。

 

但是往往需求就不是那么简单的, 我们的实体类是hibernate的实体类,里面进行一些实体之间的关联,同时还有我们对枚举类型进行了一些特殊的封装,使hibernate保存在数据库中的数据是我们自定义的一个数字或者是一个标识符,这个大家都肯定都能想到,这些特殊的类型无法直接从数据库保存的值直接转化成我们自己的类型,需要我们对这种特殊类型进行一些改造。在进行改造的时候,我们应该先知道怎么从columnToPerporty这样的一个过程。

 

首先,我们知道hibernate对实体的映射是通过反射来实例化实体类,获取属性来设置值的,dbutils也是一样,他通过一个默认的规则使得列名和属性名对应,然后找到需要设置值的属性名,当在实体中没有找到相关的属性时,就不设置,这样首先要解决的问题是,怎么把他的属性映射的默认规则改成我们自己的规则。对于这种属性和列对应的处理我们通过查阅BeanProcessor的源码来看默认的映射规则。

 

 

protected int[] mapColumnsToProperties(ResultSetMetaData rsmd,
            PropertyDescriptor[] props) throws SQLException {

        int cols = rsmd.getColumnCount();
        int columnToProperty[] = new int[cols + 1];
        Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);

        for (int col = 1; col <= cols; col++) {
            String columnName = rsmd.getColumnLabel(col);
            if (null == columnName || 0 == columnName.length()) {
              columnName = rsmd.getColumnName(col);
            }
            for (int i = 0; i < props.length; i++) {

                if (columnName.equalsIgnoreCase(props[i].getName())) {
                    columnToProperty[col] = i;
                    break;
                }
            }
        }

        return columnToProperty;
    }
 

找到这个方法,这个方法我一开始看的时候感觉怪怪的,怎么是个嵌套循环呢,不知道当时作者是怎么想的,我想了想,这样的嵌套循环最多的次数可能为 cols.length * cols.length,当然对于计算机来说这也许算不了什么,但是这样的写法的可读性毕竟是不太好的,所以我在改造的时候对这段代码进行重载,再来看看这段代码里面的关键的一句,让我们知道dbutils的列与属性的对应的默认的规则:columnName.equalsIgnoreCase(props[i].getName(),将列名与属性名称忽略大小写进行比较,如果比较相等,就对应上了。

 

麻烦来了,这样的对应关系跟我们的系统的hibernate的映射规则不匹配,在实体中我们使用驼峰表达式,而在数据库中我们遵循驼峰使用_来分开,还有更麻烦的是我们的hibernate中的映射不全遵循驼峰表达式(例如:phoneOrTel-->phone_or_tel),还有一些关联的实体是直接通过注解写在属性上方来进行列名映射的。如果这个映射规则不改变,那我们就无法使用BeanProcessor来进行记录集与bean的直接转换了。或许可以通过一个结果处理器重新来写,但是这样的话每张表都需要写ResultHandler来处理,而且需要一个

列一列进行转换,这样产生出来的工作量非常大,而且就算是全部映射完了,也不好去维护。于是考虑对BeanProcessor的方法进行覆盖重写,来满足我们映射规则上

的需求,

 

 

@Override
	protected int[] mapColumnsToProperties(ResultSetMetaData rsmd,
			PropertyDescriptor[] props) throws SQLException {
        int cols = rsmd.getColumnCount(); 
        int columnToProperty[] = new int[cols + 1];
        Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);
        
		Map<String, Integer> properMap = new HashMap<String, Integer>();
		for (int i = 0; i < props.length; i++) {
			properMap.put(props[i].getName(), i);
		}

		for (int col = 1; col <= cols; col++) {
			String columnName = rsmd.getColumnLabel(col);
			if (null == columnName || 0 == columnName.length()) {
				columnName = rsmd.getColumnName(col);
			}
			columnName = convert(columnName);
			Integer descriptorIndex = properMap.get(columnName);
			if (descriptorIndex != null) {
				columnToProperty[col] = descriptorIndex.intValue();
			}
		}
        return columnToProperty;
	}

 

 

这段代码首先是对嵌套循环做了一个改变,然后是对映射的规则进行了重写,主要的逻辑在convert这个方法里面,主要代码如下所示。

 

 

private String convert(String columnName) {
		columnName = columnName.toLowerCase();
		//先从MAP中找出对应的属性值
		XmlBeanInfo columnToPerporty = this.tableColumnMap.get(columnName);		
		if (columnToPerporty != null) {
			return columnToPerporty.getProperty();
		}
		String regexValue = "(_\\w)\\w?";
		Pattern pattern = Pattern.compile(regexValue);
		Matcher matcher = pattern.matcher(columnName);
		while (matcher.find()) {
			String value = matcher.group(1);
			String upCaseValue = value.replaceAll("_", "").toUpperCase();
			columnName = columnName.replaceFirst(value, upCaseValue);
		}
		// 替换成驼峰表达式
		return columnName;
	}
 

这样我们就完成了对映射规则的改变了,至于实体之间关联使用注解在属性上方进行自定义映射来进行匹配的,这个等我有时间的时候再写个专题来介绍,也不复杂。

 

随后我们要解决的问题是关于实体中的一些特殊的类型,在dbutils中没有实现的,比如java.sql.date-->calendar类型的转换啊,如果不转化,值将无法反射进去,我们先来看看在dbutils中是怎么通过反射将值设置进入实体的。请看下面的代码

 

    private <T> T createBean(ResultSet rs, Class<T> type,
            PropertyDescriptor[] props, int[] columnToProperty)
            throws SQLException {

        T bean = this.newInstance(type);

        for (int i = 1; i < columnToProperty.length; i++) {

            if (columnToProperty[i] == PROPERTY_NOT_FOUND) {
                continue;
            }

            PropertyDescriptor prop = props[columnToProperty[i]];
            Class<?> propType = prop.getPropertyType();

            Object value = this.processColumn(rs, i, propType);

            if (propType != null && value == null && propType.isPrimitive()) {
                value = primitiveDefaults.get(propType);
            }

            this.callSetter(bean, prop, value);
        }

        return bean;
    }

 

这段代码告诉我们,对每个列进行类型的转化之后设置进入实体属性中,这个方法是private方法,是不能被重写的,而processColumn这个方法是可以被重写的,我们对这个方法进行一些改造,就能满足我们的要求了,改造之后的代码如下:

 

@Override
	protected Object processColumn(ResultSet rs, int index, Class<?> propType) throws SQLException {
		Set<Class<?>> classSet = specialClassMap.keySet();
		boolean containsInterface = classSet.contains(propType);
		Class<?> containsClass = null;
		if (!containsInterface) {
			Class<?>[] allInterfaceArr = propType.getInterfaces();
			for (Class<?> inte : allInterfaceArr) {
				if (classSet.contains(inte)) {
					containsInterface = true;
					containsClass = inte;
				}
			}
		} else {
			containsClass = propType;
		}

		if (containsInterface) {
			SpecialTypeHandle<?> handle = specialClassMap.get(containsClass);
			if (handle == null) {
				throw new IllegalArgumentException("The SpecialTypeHandle Cannot Be Null");
			}
			if (!propType.isPrimitive() && rs.getObject(index) == null) {
				return null;
			}
			Object o = rs.getObject(index);
			Object result = handle.getResultObject(propType,o);
			return result;
		}
		return super.processColumn(rs, index, propType);
	}

 

这段代码中涉及到的specialClassMap是在子类中注入的属性,这个属性就是我们定义的特殊的类的name与SpecialTypeHandle的键值对,SpecialTypeHandle是一个特殊的接口,这个方法大概的意思是:先在注入的特殊的类中找是否有参数中传入的type的类型,如果找到了,就去map中找处理这个特殊类的处理器,然后通过处理器来处理值,将处理后的返回值返回。所以SpecialTypeHandle是一个接口,接口定义如下:

 

 

public interface SpecialTypeHandle<T extends Object> {

	public Class<T> getClassSimpleName();
	
	public T getResultObject(Class<?> eClass,Object value);
}
 

这样不管是什么样的特殊类型,只要我们定义一个类型的处理器,然后把这个类型加入到特殊类型的MAP中去,就能将正确的值反射进入实体的属性中。贴一个处理Calendar的handle的实现

 

 

public class CalendarType implements SpecialTypeHandle<Calendar> {

	@Override
	public Class<Calendar> getClassSimpleName() {
		return Calendar.class;
	}
	
	@Override
	public Calendar getResultObject(Class<?> eClass, Object value) {
		Calendar calendar = Calendar.getInstance();
		if (eClass.getName().equals("java.sql.Timestamp")) {
			TimeStamp timeStamp = (TimeStamp) value;
			calendar.setTimeInMillis(timeStamp.getTime());
		} else if (eClass.getName().equals("java.sql.Date")) {
			Date dateValue = (Date) value;
			calendar.setTime(dateValue);
		}
		return calendar;
	}

}
 

至此,我们就完成了大部分封装工作了,是不是很简单,我们还需要进行一些封装,那就是将方法封装出去让用户使用。由于在项目中我们使用了spring,所以整合进spring,这非常的方便,更棒的是有很多需要写代码的工作我们也省却了。比如设置那个特殊类型的map的工作,还有实例化handle的工作,这些都可以让spring来做了。再往上封装就非常的简单了,每个人都有自己的封装的习惯,这里我也就不贴出相关的封装的代码,有需要的可以找我要个。

 

总结一下:dbutils非常简单,通过简单的改造之后我们也能实现很强大的功能,这是我们想要的结果。我们在进行框架性的东西改造或者封装的时候,我们要对需要处理的框架要透彻的了解,这样可以让我们更加简单,高效,正确的,漂亮的完成工作,当然本屌丝水平有限,班门弄斧的还请见谅!顺便说一句,我们已经将dbutils写成了一个比较通用的orm组件了,测试环境下写测试用例已经很方便了。

 

 

分享到:
评论
6 楼 jianyan163qq 2014-08-11  
XmlBeanInfo columnToPerporty = this.tableColumnMap.get(columnName);    
XmlBeanInfo 是什么? tableColumnMap是哪里来的?
5 楼 haiyangyiba 2014-06-04  
spring + dbutils对事务怎么控制?  能否应用spring的声明式事务管理?
zyz251314 写道
zhb870815 写道
haiyangyiba 写道

楼主我更关注这个呀
如果应用中已经集成了spring,那将dbutils集成进来就非常的简单了,直接通过spring对queryRunner进行管理,这里就不细说了!会用spring的人都知道该怎么做了

不知道怎么搞



联系q:1203424580




spring + dbutils对事务怎么控制?  能否应用spring的声明式事务管理?


查询使用dbutils,至于更新操作,使用spring的jdbcTemplate实现,可以控制事物
4 楼 zhb870815 2013-12-04  
zyz251314 写道
zhb870815 写道
haiyangyiba 写道

楼主我更关注这个呀
如果应用中已经集成了spring,那将dbutils集成进来就非常的简单了,直接通过spring对queryRunner进行管理,这里就不细说了!会用spring的人都知道该怎么做了

不知道怎么搞



联系q:1203424580




spring + dbutils对事务怎么控制?  能否应用spring的声明式事务管理?



事务可以使用spring的声明式事务,也可以使用spring的事务模版,这个只是一个orm组件,跟事务没有太大的关系,就是之前怎么用事务的,这里还是怎么养
3 楼 zyz251314 2013-05-10  
zhb870815 写道
haiyangyiba 写道

楼主我更关注这个呀
如果应用中已经集成了spring,那将dbutils集成进来就非常的简单了,直接通过spring对queryRunner进行管理,这里就不细说了!会用spring的人都知道该怎么做了

不知道怎么搞



联系q:1203424580




spring + dbutils对事务怎么控制?  能否应用spring的声明式事务管理?
2 楼 zhb870815 2013-01-19  
haiyangyiba 写道

楼主我更关注这个呀
如果应用中已经集成了spring,那将dbutils集成进来就非常的简单了,直接通过spring对queryRunner进行管理,这里就不细说了!会用spring的人都知道该怎么做了

不知道怎么搞



联系q:1203424580
1 楼 haiyangyiba 2012-07-24  

楼主我更关注这个呀
如果应用中已经集成了spring,那将dbutils集成进来就非常的简单了,直接通过spring对queryRunner进行管理,这里就不细说了!会用spring的人都知道该怎么做了

不知道怎么搞

相关推荐

    dbutils dbutils dbutils dbutils

    dbutils dbutils dbutils dbutils

    dbutils的jar包和源码

    dbutils的jar包和源码

    DBUtils 数据连接池源码

    DBUtils 数据连接池源码,已经提供了很多常用的方法,直接调用,可以实现,有test 测试类,已经注明了如果在你的程序中初始化,非常轻巧,方便。通用所有数据库,因为分类无法选择全部。

    commons_dbutils使用说明

    详细讲解了commons_dbutils的使用。

    commons-dbutils组件包与源码

    commons-dbutils组件包与源码,人个收集,提供给大家共同学习,共同进步。

    DBUtils使用和jar包.rar

    压缩包中存在DbUtils使用说明文档、jar包以及一个使用样例。commons-dbutils 是 Apache 组织提供的一个开源 JDBC 工具类库,对传统操作数据库的类进行二次封装,可以把结果集转化成List。

    dbutils-1.3架包和源码

    dbutils-1.3架包和源码,是apache的一个开源架包

    dbutils文档和源码及jar包

    Common Dbutils是操作数据库的组件,对传统操作数据库的类进行二次封装,可以把结果集转化成List。 补充一下,传统操作数据库的类指的是JDBC(java database connection:java数据库连接,java的数据库操作的基础API...

    DButils的使用

    DBUtils使用的步骤,下载,添加,配置,使用,每一步均讲解的清晰易懂。

    dbutils的使用.doc

    dbutils的说明自己查吧 dbutils的使用.doc

    Dbutils学习源码总结

    apache下面有很多值得学习的开源项目,尤其是commons系列,在此,特封装了其组织下的dbutils根据,方便了喜欢使用sql开发的java朋友,里面有各种实用的封装类和对数据库操作的接口,欢迎下载!

    DBUtils数据库的使用

    DBUtils是java编程中的数据库操作实用工具,小巧简单实用。

    dbutils + oracle 增删改查批量插入示例

    1、包含示例war包、文件夹 2、示例所需要的SQL语句 3、dbutils开发包及其源码 4、eclipse + oracle 测试通过

    DBUtils介绍与使用相关的JAR包

    我的博客文章"DBUtils介绍与使用"相关的JAR包,我的博客文章"DBUtils介绍与使用"相关的JAR包

    DButils 的源代码

    DButils 的源代码! hsqldb-2.0.0.版本的!

    commons-dbutils-1.6的jar包、源码、文档说明.zip

    commons-dbutils-1.6的jar包、源码、文档说明.zip

    JAVA-JDBC-DbUtils教程简单到精通!

    JAVA-JDBC-DbUtils教程简单到精通!包含教学视频,源码,笔记。超值。

    DbUtils扩展源码

    ApacheCommos的DbUtils是一个简单好用的轻量级的数据库操作工具,该项目的主页是:http://commons.apache.org/dbutils/,关于它的信息可以从那里获取. dbutils可以把查询出来的结果集映射成Bean的List,这是个很有用的...

    DBUtils文档+源码

    DButils的文档和源代码都有了,不会的查文档,关联源代码等。

    python类DBUtils安装包

    DBUtils 是一套允许线程化 Python 程序可以安全和有效的访问数据库的模块。DBUtils已经作为 Webware for Python 一部分用来结合 PyGreSQL 访问 PostgreSQL 数据库,当然他也可以用在其他Python应用程序中来访问 DB-...

Global site tag (gtag.js) - Google Analytics