概述
从 JDBC 规范上看,其对数据访问层有相当简洁的抽象:
- 连接 Connection
- 语句 Statement
- 结果集 Result Set
因此,我们对数据库做的事情无非:连接数据库、执行语句、拿到结果。如下图所示。
故这里的 DB helper 仅仅是基于 JDBC 的简单封装,只有三个关键的类,其特性有:
- 提供增改查三种静态函数,可以传值参数。query/queryList 查询函数主要就是将数据库的查询出来的 ResultSet 转化为 Map 或 List;新增记录返回主键;
- 一律采用 PreparedStatment,以防止 SQL 注入及自动值类型转换;
- 主键为 java.io.serializable 类型,兼容 int/long/string 类型;
- 提供简易的半 ORM 方案,也就是说,这并不是完全意义上的 ORM。返回的类型可以有 Map 或者 Bean;
- 提供简单的分页功能; 支持 JDBC 4 以及 Java 7 autoClose 自动关闭资源,无须手动 close();
- 打印运行时出错的 SQL 语句,可以将其直接拷贝到数据库客户端上进行调试,并提供格式化 SQL 工具函数,打印日志时更美观;
- 提炼了 MyBatis 的 SQL Builder 包,可以通过 Java 动态拼接 SQL;
- 当前暂不提供批量操作 TODO。
数据库连接 JdbcConnection
获取数据库连接对象 DataSource
通过 JDBC 的 DriverManager.getConnection 获取数据库连接对象。提供如下方法:
Connection getConnection(String jdbcUrl);
Connection getConnection(String jdbcUrl, Properties props);
其中 jdbcUrl 参数形如 jdbc:sqlite:c:\project\foo\work\work.sqlite,无须传入数据库驱动字符串 Driver 名称(如 “org.sqlite.JDBC”),这是采用了 JDBC4 新特性的缘故,如果你的 JDK 不支持 JDBC4,可以退回旧写法:
Connection conn = null;
try {
if (props == null)
conn = DriverManager.getConnection(jdbcUrl);
else
conn = DriverManager.getConnection(jdbcUrl, props);
LOGGER.info("数据库连接成功: " + conn.getMetaData().getURL());
} catch (SQLException e) {
LOGGER.warning("数据库连接失败!", e);
try { // jdbc 4的新 写法可不用 Class.forName,如果不支持,退回旧写法
Class.forName(driver);
} catch (ClassNotFoundException e1) {
LOGGER.warning("创建数据库连接失败,请检查是否安装对应的 Driver:{0}。", driver);
}
}
数据库连接池
数据源提供了一种简单获取数据库连接的方式,并能在内部通过一个池的机制来复用数据库连接。当前我们这个框架使用了 Tomcat 自带 Pool(适用于 Web),你也可以使用其他如 Druid 的数据库连接池。
// 简写方式
javax.sql.DataSource ds = (javax.sql.DataSource)new InitialContext().lookup("java:/comp/env/jdbc/derby");
// 通过数据源对象获得数据库连接对象
Connection connection = ds.getConnection();
数据库数据的增删改查
查询
先连接数据库:
Connection conn = getConnection("jdbc:sqlite:c:\\project\\foo\\work\\work.sqlite");
查询单个记录,将其转换为 map。
Map<String, Object> info = Helper.query(conn, "SELECT * FROM news WHERE id = ?", 1);
支持带参数的 SQL,如:
Map<String, Object> info = Helper.query(conn, "SELECT * FROM news WHERE id = 1");
查询多个记录,将其转换为 List
List<Map<String, Object>> list = Helper.queryList(conn, "SELECT * FROM news WHERE type = 1");
// 或
List<Map<String, Object>> list = Helper.queryList(conn, "SELECT * FROM news WHERE type = ?", 1);
除 Connection 外,涉及的 statement、resultset 对象均采用 Java 7 autoClose 自动关闭资源,无须手动 close()。
创建&修改
为防止 SQL 注入,推荐使用 PreparedStatement。下面是 JDBC 经典的创建方法。
String createSql = "INSERT INTO myblog (name, content, uid, brief, catalog, createDate) values(?, ?, ?, ?, ?, datetime('now'))";
try (PreparedStatement ps = conn.prepareStatement(createSql);) {
ps.setString(1, request.getParameter("name"));
ps.setString(2, request.getParameter("content"));
ps.setString(3, Util.getUUID());
ps.setString(4, request.getParameter("brief") == null ? "" : request.getParameter("brief"));
ps.setInt(5, Integer.parseInt(request.getParameter("catalog")));
if (ps.executeUpdate() == 0) {
request.setAttribute("errMsg", "创建失败!");
} else {
request.setAttribute("okMsg", "创建成功!");
}
}
据此,我们封装的 create/update 使用方法如下:
// 创建一个 bean
// 返回的主键为 java.io.serializable 类型,兼容 int/long/string 的情况
Serializable newlyId = Helper.create(conn, "INSERT INTO news (name) VALUES (?)", "test2");
System.out.println(newlyId);
int id = (int)newlyId;
assertNotNull(id);
// 修改一个 bean
int updated;// 影响的行数
updated = Helper.update(conn, "UPDATE news SET name = ? WHERE id = ?", "Hi", id);
updated = Helper.update(conn, "UPDATE news SET name = 'hi' WHERE id = 60");
assertEquals(1, updated);
删除
删除方法是本类唯一封装了 SQL 语句的方法,故需要传入表名 tableName。其用法如下:
/**
* 删除一条记录
*
* @param conn
* 数据库连接对象
* @param tableName
* 表名
* @param id
* ID
* @return 是否删除成功
*/
public static boolean delete(Connection conn, String tableName, Serializable id) ;
// 例子
Helper.delete(conn, "news", id);
分页
自动生成求记录总数的 SQL,如果 > 0 则允许进行分页;可指定起始数、每页记录行数。
SimplePager sp = new SimplePager(conn, sql, request.getParameter("start"));
PageResult<Map<String, Object>> pr = sp.getResult();
返回 PageResult 对象供 页面 渲染。PageResult 是一个 Java Bean。
ORM
ORM 也是围绕 CRUD 的,只不过实体类型换为 bean。
查询:
SimpleORM<News> simpleORM = new SimpleORM<>(conn, News.class);
News news = simpleORM.query("SELECT * FROM news WHERE id = ?", 23);
System.out.println(news.getName());
List<News> allNews = simpleORM.queryList("SELECT * FROM news");
System.out.println(allNews.get(23).getName());
增加、修改、删除:
News news = new News();
news.setName("标题一");
news.setIntro("hihi");
SimpleORM<News> simpleORM = new SimpleORM<>(conn, News.class);
int newlyId = (int)simpleORM.create(news, "news");
System.out.println(newlyId);
news.setName("标题二");
news.setId(newlyId); // 修改刚刚生成的记录
int effectRows = (int)simpleORM.update(news, "news");
System.out.println(effectRows);
assertTrue(simpleORM.delete(news, "news"));
当前 ORM 内部使用了不少约定的做法,例如 id 字段的名称写死了,没有允许修改——像这些是大家要注意的。
推荐参考项目 Memory:https://git.oschina.net/bitprince/memory。