`
zhb870815
  • 浏览: 25174 次
  • 性别: 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 + oracle 增删改查批量插入示例

    例如,使用`QueryRunner`的`batch`方法,可以一次性提交多条SQL,减少了网络通信和数据库解析的开销。 在Eclipse中测试这个示例时,需要确保已经配置了Oracle JDBC驱动,并且设置好数据库连接参数。测试过程可能...

    dbutils-1.7 bin和src

    描述中的"commons-dbutils-1.7-bin.zip"和"commons-dbutils-1.7-src.zip"是两个具体的压缩文件,分别对应DBUtils的1.7版本的二进制和源码打包。`bin.zip`包含了运行时所需的jar文件,而`src.zip`则包含所有源代码...

    xUtils源码

    DbUtils的源码中,我们可以看到它使用了SqlBuilder来构建SQL语句,通过反射机制自动处理实体类与数据库表的映射,大大提高了开发效率。 3. **ViewUtils**:实现了注解驱动的视图注入,使得在Activity或Fragment中...

    org.apache.commons 的 jar 包 源码

    总的来说,分析和学习 "org.apache.commons" 的源码是提升Java开发技能的好方法,它可以帮助你更好地理解和应用这些广泛使用的工具类库,提高编程效率和代码质量。同时,参与开源社区,提交补丁或新功能,也是贡献...

    xutils源码以及说明

    本文将深入探讨XUtils的源码,解析其核心功能,并提供常见用法的示例。 ### 1. 图片加载模块 XUtils中的图片加载模块基于`BitmapLruCache`实现,通过内存缓存和磁盘缓存策略,实现了高效、低耗的图片加载。关键类...

    Java——Demo智能火车票购票系统源码.zip

    【Java——Demo智能火车票购票系统源码】是一个基于Java编程语言开发的示例项目,旨在展示如何构建一个简单的火车票预订系统。该系统利用了数据库技术,具体使用了MySQL作为后台数据存储,并且通过Java的JDBC(Java ...

    Android项目源码使用xutils框架写的天气预报.zip

    从天气API获取的数据通常以JSON格式返回,开发者使用XUtils的JsonUtils解析这些数据,提取如温度、湿度、风向等信息。 5. **UI设计**: 应用界面可能包含多个布局文件,如MainActivity的XML布局,展示城市的天气...

    android最火开源库xUtils源码

    **Android最火开源库xUtils源码解析** xUtils是一个非常流行的Android开发工具库,它集成了多种常用功能,如数据库操作、视图处理、网络请求以及图片处理等。这个库极大地简化了开发者的工作,提高了开发效率。接...

    Apache Common CLI 1.2 源码

    6. **HelpFormatter类**:提供默认的帮助信息格式化,当用户请求帮助或出现解析错误时,会打印出关于命令行接口的使用说明。 7. **Args类和ArgUtils**:提供了辅助方法,用于处理非选项参数(即命令行中的其他文本...

    xUtils-master(xUtils源码)

    二、xUtils的源码解析 1. **注解处理器**:xUtils大量使用了Java注解,如@Table、@Column等,这些注解在编译时被注解处理器处理,生成相应的数据库表结构或者网络请求配置。 2. **异步处理**:为了提高性能和用户...

    android开发工具集合类xutils源码

    5. **异常处理**:源码中包含了对各种可能出现的异常情况的处理,如网络异常、解析异常等,提供了友好的错误提示。 深入研究XUtils的源码,不仅可以帮助你理解Android开发中的常用技巧,还能让你学会如何优化代码,...

    apache-commons源码及jar文件

    Commons-Digester 是一个 XML-Java对象的映射工具,用于解析 XML配置文件. Discovery Commons-Discovery 提供工具来定位资源 (包括类) ,通过使用各种模式来映射服务/引用名称和资源名称。. EL Commons-EL 提供在...

    xUtils3.5源码

    《深入解析xUtils3.5源码》 xUtils3.5是一款强大的Android开发工具库,它集成了图片加载、数据库操作、网络请求等多种功能,极大地简化了Android开发者的工作流程。本文将对xUtils3.5的源码进行深度剖析,帮助读者...

    java检查sql语法是否正确

    Apache Commons DBUtils提供了一个`QueryRunner`类,它同样使用JDBC API,但提供了更友好的异常处理和更方便的批处理功能。在使用`QueryRunner`时,我们可以通过捕获`SQLException`来检查SQL语法: ```java import ...

    java之表反向生成类

    在Java中,有许多开源工具可以实现这一功能,如Hibernate的`hibernate-tools`模块、MyBatis的`mybatis-generator`、Apache的`DBUtils`等。它们提供了API或命令行工具,根据数据库元数据生成Java实体类、Mapper接口...

    java根据数据库自动生成vo 类

    在Java开发中,VO(Value Object)类通常用于在应用程序的不同层之间传递数据,比如从数据库获取的数据到前端展示。自动生成VO类可以大大提高开发效率,避免手动编写大量的getter、setter方法和其他样板代码。本篇将...

    自己写的几个小JAVA程序

    这些小程序可能涵盖了Java基础、数据库操作、前端交互等多个方面,对于初学者来说,是很好的学习材料,可以通过源码了解实际项目的组织结构和编程技巧。同时,对于有经验的开发者,这些程序也可以作为参考,快速搭建...

Global site tag (gtag.js) - Google Analytics