端午没事儿干扩充了下Hutool的DB部分,原来只是一个简单的SQL运行器,如今加入了方言支持,封装了增删改查,引入Session从而支持事务,能够说工程量巨大,在封装过程总我仍是参考了Jodd的DbOom、Jfinal的ActiveRecord、Apache Commons-DbUtils,吸收优势,剔除我以为没用的,再加入些本身的想法,尽可能作到简单和灵活。mysql
因为上次已经将1.0.0版的Hutool提交到了Maven中央库,且此次也是一个重大的改进,因此把此次的更新所有放到1.1.0版本中,考虑到未彻底测试,因此你能够在Github上clone下来使用,或者看下个人代码,体会下个人思想以及设计哲学(哲学这个词好有逼格……)。git
考虑到Hibernate作ORM的复杂性,它想把一个对象映射到数据库,再加上各类外键的复杂对应关系,当时学习的时候整的我焦头烂额,并且其数据库链接配置所有放在xml里,须要链接池插件去为它开发对应的插件,显然这样作太霸道了,总之这种灵活性的缺失,致使在使用Hibernate的时候必须按照它指定的思路和方式走,痛苦万分啊,例如你执行一条SQL语句,查询的结果是个让人百思不得其解的列表,难用的要死。后来我便倾向于Apache Commons-DbUtils,谁让业务简单呢,没几张表,简简的看了其源码,作了些简单的改进放到个人Hutool里来了,而后就看了Jfinal的ActiveRecord,那段时间正在看Python的一个框架Django,发现其殊途同归之妙的ORM方式:将数据库表映射为一个Map,而不是一个对象。这样的灵活性大大的增长,字段也更加灵活。因而按照这个思想开始动工,封装增删改查的经常使用方法。github
在ORM中,我把一张表中的一条数据映射成为一个叫作Entity的类,继承自HashMap,key是字段名,value是Object类型,字段值,这样一个Entity对象就是数据库表中的一条记录,固然这个对象中还有个字段是表的名字,方便以后的操做。以后对数据库增删改查操做的对象大可能是这个。sql
这个对象充当着两种角色,一个是数据的载体,表示一条数据,另外一个就是where语句的条件,固然,Entity对象只支持 =
操做,更复杂的操做我之后再想别的办法。充当where条件时,key依旧是字段名,value是字段条件值。例如:数据库
Entity where = Entity.create(TABLE_NAME).set("条件1", "条件值");
表示的where语句是:apache
WHERE `条件1` = 条件值
固然到时候会用PreparedStatement,不会出现SQL注入。django
这两个对象主要是描述数据库表结构的,暂时和ORM自己没啥关系,只是当你想得到一些字段信息的时候,这样来得到表结构信息:session
/** * 得到表的元数据 * * @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); }
总体分为几部分架构
DataSource
SqlExecutor
SqlConnRunner
SqlRunner
Session
handler
DbUtil
还有就是没有列出来的dialect(数据库方言),我会根据给定的DataSource、Connection等对象自动识别是什么数据库,而后使用不一样的方言构造SQL语句,暂时支持的数据库有MySQL、Oracle、SqlLite3,固然若是识别失败会用ANSI SQL,这样遇到不支持的数据,能够搞定大部分方法。框架
下面解释下:
不管是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; }
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); } }
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 { } }
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(); } }
handler
此包中有个叫作RsHandler
的接口,传入ResultSet对象,返回什么则在handle方法中本身指定。 实现的类有:
EntityHandler 转换为Entity列表
NumberHandler 当使用select count(1)
这类语句的时候,或者返回只有一个结果,且为数字结果的时候,用这个handler
SingleEntityHandler 返回一条记录的时候用这个
数据库的一些工具方法汇总 DbUtil
提供一些工具方法,最经常使用的就是close
方法了,因为JDK7才把ResultSet``Statement``PreparedStatement``Connection
这几个接口实现了Closeable接口,因此以前只能判断类型再去关闭,这样一个close方法能够关闭多个对象。