文档章节

Hutool之对JDBC的ORM封装

路小磊
 路小磊
发布于 2014/06/03 00:46
字数 2563
阅读 3.1K
收藏 9

开篇

端午没事儿干扩充了下Hutool的DB部分,原来只是一个简单的SQL运行器,现在加入了方言支持,封装了增删改查,引入Session从而支持事务,可以说工程量巨大,在封装过程总我还是参考了Jodd的DbOom、Jfinal的ActiveRecord、Apache Commons-DbUtils,吸取优点,剔除我觉得没用的,再加入些自己的想法,尽量做到简单和灵活。

版本支持

由于上次已经将1.0.0版的Hutool提交到了Maven中央库,且这次也是一个重大的改进,所以把这次的更新全部放到1.1.0版本中,考虑到未完全测试,所以你可以在Github上clone下来使用,或者看下我的代码,体会下我的思想以及设计哲学(哲学这个词好有逼格……)。

由来

考虑到Hibernate做ORM的复杂性,它想把一个对象映射到数据库,再加上各种外键的复杂对应关系,当时学习的时候整的我焦头烂额,而且其数据库连接配置全部放在xml里,需要连接池插件去为它开发对应的插件,显然这样做太霸道了,总之这种灵活性的缺失,导致在使用Hibernate的时候必须按照它指定的思路和方式走,痛苦万分啊,例如你执行一条SQL语句,查询的结果是个让人百思不得其解的列表,难用的要死。后来我便倾向于Apache Commons-DbUtils,谁让业务简单呢,没几张表,简简的看了其源码,做了些简单的改进放到我的Hutool里来了,然后就看了Jfinal的ActiveRecord,那段时间正在看Python的一个框架Django,发现其异曲同工之妙的ORM方式:将数据库表映射为一个Map,而不是一个对象。这样的灵活性大大的增加,字段也更加灵活。于是按照这个思想开始动工,封装增删改查的常用方法。

对象解释

1. Entity

在ORM中,我把一张表中的一条数据映射成为一个叫做Entity的类,继承自HashMap,key是字段名,value是Object类型,字段值,这样一个Entity对象就是数据库表中的一条记录,当然这个对象中还有个字段是表的名字,方便之后的操作。之后对数据库增删改查操作的对象大多是这个。

这个对象充当着两种角色,一个是数据的载体,表示一条数据,另一个就是where语句的条件,当然,Entity对象只支持 = 操作,更复杂的操作我以后再想别的办法。充当where条件时,key依旧是字段名,value是字段条件值。例如:

Entity where = Entity.create(TABLE_NAME).set("条件1", "条件值");

表示的where语句是:

WHERE `条件1` = 条件值

当然到时候会用PreparedStatement,不会出现SQL注入。

2. Table Column

这两个对象主要是描述数据库表结构的,暂时和ORM本身没啥关系,只是当你想获得一些字段信息的时候,这样来获得表结构信息:

    /**
 * 获得表的元数据
 * 
 * @param ds 数据源
 */
private static void getTableMetaInfo(DataSource ds) {
	// 获得当前库的所有表的表名
	List<String> tableNames = DbUtil.getTables(ds);
	Log.info("{}", tableNames);

	/*
	 * 获得表结构 表结构封装为一个表对象,里面有Column对象表示一列,列中有列名、类型、大小、是否允许为空等信息
	 */
	Table table = DbUtil.getTableMeta(ds, TABLE_NAME);
	Log.info("{}", table);
}

整体的架构

整体分为几部分

  1. 数据源 DataSource
  2. SQL执行器 SqlExecutor
  3. CRUD的封装 SqlConnRunner SqlRunner
  4. 支持事务的CRUD封装 Session
  5. 各种结果集处理类 handler
  6. 数据库的一些工具方法汇总 DbUtil

还有就是没有列出来的dialect(数据库方言),我会根据给定的DataSource、Connection等对象自动识别是什么数据库,然后使用不同的方言构造SQL语句,暂时支持的数据库有MySQL、Oracle、SqlLite3,当然如果识别失败会用ANSI SQL,这样遇到不支持的数据,可以搞定大部分方法。

下面解释下:

1. 数据源

无论是JDNI还是数据库连接池,最终给用户的都是一个DataSource,那么,我执行SQL的连接直接从数据源里拿就可以,至于数据源是JNDI还是哪种连接池我是不管的,这样大大提高了灵活性。在ds包中,我还自己封装了SimpleDataSource对象,这是不用数据库连接池的数据源,连接直接问DriverManager中拿,完全是JDBC原生的数据库连接获取操作,当然这个类由于没有连接池,仅供测试或打开关闭连接非常少的场合使用。当然,我还提供了一个DruidDS把数据库配置以及连接池配置放在配置文件里,更加方便。

    /**
 * @return 获得数据源样例方法
 */
private static DataSource getDataSource() {
	/*
	 * 获得数据源,可以使用Druid、DBCP或者C3P0数据源
	 * 我封装了Druid的数据源,在classpath下放置db.setting和druid.setting文件 
	 * 详细格式请参考doc/db-example.setting和doc/db/example.setting 
	 * 如果没有druid.setting文件,使用连接池默认的参数 可以配置多个数据源,用分组隔离
	 */
	DataSource ds = DruidDS.getDataSource("test");

	//当然,如果你不喜欢用DruidDS类,你也可以自己去实例化连接池的数据源 具体的配置参数请参阅Druid官方文档
	DruidDataSource ds2 = new DruidDataSource();
	ds2.setUrl("jdbc:mysql://fedora.vmware:3306/extractor");
	ds2.setUsername("root");
	ds2.setPassword("123456");
	ds = ds2;

	return ds;
}

2. SQL执行器 SqlExecutor

这是一个静态类,里面的静态方法只有两种:执行非查询的SQL语句和查询的SQL语句

    /**
 * SqlExecutor样例方法<br>
 * 如果你只是执行SQL语句,使用SqlExecutor类里的静态方法即可
 * 
 * @param ds 数据源
 */
private static void sqlExecutorDemo(DataSource ds) {
	Connection conn = null;
	try {
		conn = ds.getConnection();
		// 执行非查询语句,返回影响的行数
		int count = SqlExecutor.execute(conn, "UPDATE " + TABLE_NAME + " set field1 = ? where id = ?", 0, 0);
		log.info("影响行数:{}", count);
		// 执行非查询语句,返回自增的键,如果有多个自增键,只返回第一个
		Long generatedKey = SqlExecutor.executeForGeneratedKey(conn, "UPDATE " + TABLE_NAME + " set field1 = ? where id = ?", 0, 0);
		log.info("主键:{}", generatedKey);

		/* 执行查询语句,返回实体列表,一个Entity对象表示一行的数据,Entity对象是一个继承自HashMap的对象,存储的key为字段名,value为字段值 */
		List<Entity> entityList = SqlExecutor.query(conn, "select * from " + TABLE_NAME + " where param1 = ?", new EntityHandler(), "值");
		log.info("{}", entityList);
	} catch (SQLException e) {
		Log.error(log, e, "SQL error!");
	} finally {
		DbUtil.close(conn);
	}
}

3. CRUD的封装 SqlConnRunner SqlRunner

这两个类有些相似,里面都封装了增、删、改、查、分页、个数方法,差别是SqlConnRunner需要每个方法都传Connection对象,而SqlRunner继承自SqlConnRunner,在传入DataSource会自动获取Connection对象。Demo如下:

    /**
 * SqlRunner是继承自SqlConnRunner的(SqlConnRunner继承自SqlExecutor),所以相应的方法也继承了下来,可以像SqlExecutor一样使用静态方法<br>
 * 当然,SqlRunner更强大的功能在于对Entity对象做CRUD,避免写SQL语句。 SqlRunner需要实例化
 * 
 * SqlRunner同时提供了带Connection参数的CRUD方法,方便外部提供Connection对象而由使用者提供事务的操作
 * 
 * @param ds 数据源
 */
private static void sqlRunnerDemo(DataSource ds) {
	Entity entity = Entity.create(TABLE_NAME).set("字段1", "值").set("字段2", 2);
	Entity where = Entity.create(TABLE_NAME).set("条件1", "条件值");

	try {
		SqlRunner runner = SqlRunner.create(ds);
		// 指定数据库方言,在此为MySQL
		runner = SqlRunner.create(ds);

		// 增,生成SQL为 INSERT INTO `table_name` SET(`字段1`, `字段2`) VALUES(?,?)
		runner.insert(entity);

		// 删,生成SQL为 DELETE FROM `table_name` WHERE `条件1` = ?
		runner.del(where);

		// 改,生成SQL为 UPDATE `table_name` SET `字段1` = ?, `字段2` = ? WHERE `条件1` = ?
		runner.update(entity, where);

		// 查,生成SQL为 SELECT * FROM `table_name` WHERE WHERE `条件1` = ? 第一个参数为返回的字段列表,如果null则返回所有字段
		List<Entity> entityList = runner.find(null, where, new EntityHandler());
		log.info("{}", entityList);

		// 分页,注意,ANSI SQL中不支持分页!
		List<Entity> pagedEntityList = runner.page(null, where, 0, 20, new EntityHandler());
		log.info("{}", pagedEntityList);

		// 满足条件的结果数,生成SQL为 SELECT count(1) FROM `table_name` WHERE WHERE `条件1` = ?
		int count = runner.count(where);
		log.info("count: {}", count);
	} catch (SQLException e) {
		Log.error(log, e, "SQL error!");
	} finally {
	}
}

4. 支持事务的CRUD封装 Session

Session非常类似于SqlRunner,差别是Session对象中只有一个Connection,所有操作也是用这个Connection,便于事务操作,而SqlRunner每执行一个方法都要从DataSource中去要Connection。样例如下:

    private static void sessionDemo(DataSource ds) {
	Entity entity = Entity.create(TABLE_NAME).set("字段1", "值").set("字段2", 2);
	Entity where = Entity.create(TABLE_NAME).set("条件1", "条件值");

	Session session = Session.create(ds);
	try {
		session.beginTransaction();

		// 增,生成SQL为 INSERT INTO `table_name` SET(`字段1`, `字段2`) VALUES(?,?)
		session.insert(entity);

		// 删,生成SQL为 DELETE FROM `table_name` WHERE `条件1` = ?
		session.del(where);

		// 改,生成SQL为 UPDATE `table_name` SET `字段1` = ?, `字段2` = ? WHERE `条件1` = ?
		session.update(entity, where);

		// 查,生成SQL为 SELECT * FROM `table_name` WHERE WHERE `条件1` = ? 第一个参数为返回的字段列表,如果null则返回所有字段
		List<Entity> entityList = session.find(null, where, new EntityHandler());
		log.info("{}", entityList);

		// 分页,注意,ANSI SQL中不支持分页!
		List<Entity> pagedEntityList = session.page(null, where, 0, 20, new EntityHandler());
		log.info("{}", pagedEntityList);

		session.commit();
	} catch (Exception e) {
		session.quietRollback();
	} finally {
		session.close();
	}
}

5. 各种结果集处理类 handler

此包中有个叫做RsHandler的接口,传入ResultSet对象,返回什么则在handle方法中自己指定。 实现的类有:

  1. EntityHandler 转换为Entity列表

  2. NumberHandler 当使用select count(1)这类语句的时候,或者返回只有一个结果,且为数字结果的时候,用这个handler

  3. SingleEntityHandler 返回一条记录的时候用这个

  4. 数据库的一些工具方法汇总 DbUtil 提供一些工具方法,最常用的就是close方法了,由于JDK7才把ResultSet``Statement``PreparedStatement``Connection这几个接口实现了Closeable接口,所以之前只能判断类型再去关闭,这样一个close方法可以关闭多个对象。

© 著作权归作者所有

路小磊

路小磊

粉丝 448
博文 55
码字总数 42397
作品 5
呼和浩特
程序员
私信 提问
加载中

评论(0)

Java工具集-hutool 2.5.0发布

Hutool 是一个Java工具包,提供了丰富的文件、日期、日志、正则、字符串、配置文件等工具方法,并封装了一套简单易用的ORM框架。 2.5.0 更新内容: 1. 谢谢@红书 提的bug,修复了在实体类转P...

路小磊
2015/03/25
3.7K
37
Hutool 5.0.0 发布,不再支持 JDK 7

Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 Hutool中的工具方法来自于每个用...

路小磊
2019/10/17
4.4K
26
hutool 2.8.1 发布,Java 工具集

Hutool 是一个Java工具包,提供了丰富的文件、日期、日志、正则、字符串、配置文件等工具方法,并封装了一套简单易用的ORM框架。 改进内容如下: 1. SecureUtil中增加mac算法的加密 2. 增加针...

路小磊
2015/05/06
2.2K
20
hutool 2.7.2 发布,Java 工具集

Hutool 是一个Java工具包,提供了丰富的文件、日期、日志、正则、字符串、配置文件等工具方法,并封装了一套简单易用的ORM框架。 改进内容如下: 增加SqlBuilder 增加HttpUtil.request方法 ...

路小磊
2015/04/14
4.7K
16
Hutool 5.2.0 发布,纪念 star 破万

Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 Hutool中的工具方法来自于每个用...

路小磊
03/06
4.4K
16

没有更多内容

加载失败,请刷新页面

加载更多

SpringBoot和SpringCloud的区别

一、SpringBoot和SpringCloud简介 1、SpringBoot:是一个快速开发框架,通过用Maven依赖的继承方式,帮助我们快速整合第三方常用框架,完全采用注解化(使用注解方式启动SpringMVC),简化X...

安然_oschina
26分钟前
13
0
IMX6ULL开发板-虚拟机安装Ubuntu系统

安装好了 VMware 虚拟机以后,我们就可以在 VMware 上安装 Ubuntu 系统了,首先我们去 Ubuntu 的 官网获取系统镜像,下载地址为:https://www.ubuntu.com/download/desktop,如下图所示: 从...

书白
34分钟前
11
0
MyBatis Plus自定义SQL使用条件构造器QueryWrapper

1.注解的方式: @Select("select * from user_collection uc left join post p on uc.post_id = p.id ${ew.customSqlSegment}")IPage<Post> selectPosts(Page page, @Param(Constants.WRAP......

code-ortaerc
38分钟前
9
0
DNS负载均衡

DNS是什么? DNS(Domain Name System)是域名和IP地址相互映射的一个分布式数据库。在DNS系统中有一个比较重要的的资源类型叫做主机记录也称为A记录。A记录是用于名称解析的重要记录,它将...

简到珍
39分钟前
12
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部