自己动手:做个数据库访问层(一)
说数据库是信息系统里最重要的部分,应当没几个人反对。最简单的访问数据库的方式就是用程序直连数据库,通过Sql进行操作,相信这也是每个程序员最初学的方法。但随着程序规模的增大,再一条条语句去写的话开发效率就有些低了,因此才有了很多框架去帮助我们操作数据库,比较流行的有Mybatis和Hibernate,也就是所谓的ORM。对于这两个工具我也不是很熟悉,只是接触过网上的一些教程,从网上接触的教程来看,感觉还是有些太复杂了,尤其是对于分页和复杂条件查询。因此就想自己动手做一个,不求大而全,只希望能完成以下功能:
1. 不用写Sql,能直接保存Java对象,还有更新与删除。
2. 支持多步查询,比如传递一个Sql数组,第一行的结果可以在第二行的查询中使用,这样就不用手动写复杂的存储过程,生成临时表了,也不用写复杂的子查询,代码更清晰,比如下面的语句
+zuZhiList select Id from ZuZhi where TypeId = 1 select r.* From RenYuan r join @zuZhiList z on z.Id = r.ZuZhiId
3. 支持条件查询,比如下面的语句
+zuZhiList select Id from ZuZhi where 1=1 {@typeId: and TypeId = @typeId}
select r.* From RenYuan r join @zuZhiList z on z.Id = r.ZuZhiId
执行时以HashMap<String, Object>存储命名参数,如果该Map里没有名为typeId的参数,或者值为Null,那么就不拼接" and TypeId = @typeId"这部分。
4. 支持自动分页,自动分析Sql语句,如果有分页关键字,就拆分成两条,一条返回总数,一条返回分页记录,比如以下语句
Select * From RenYuan where ZuZhiId = 1 page 0, 10
在执行时会被翻译成两条语句
Select count(*) totalCount From RenYuan where ZuZhiId = 1 Select * From RenYuan where ZuZhiId = 1 limit 0, 10
5. 能支持多种数据库,如SqlServer,MySql,Oracle
实现思路是这样的:
0. 写几个基础的支持方法,能够连接数据库,运行Sql,如果是查询的话还可以返回多个结果集。
1. 用反射去做,拼接Sql。
2. 如果Sql以加号开头,就创建一个临时表,临时表的字段如果Sql里有定义就用Sql里定义的,没有的话就默认为(Id int),然后把后面的select里的内容插入到临时表中。把创建的临时表名称保存在一个集合里,后面的Sql里的参数名称如果在这个集合里,就替换成临时表的名称,没有的话说明是一个普通参数。
3. 查找语句里用大括号包着的部分,再从第一部分里取出参数名称,查找命名参数里有没有该Key或者值为不为空,如果不为空,就拼接第二部分的Sql,否则就忽略。
4. 同理,查找关键字,找到From后面的部分,在前面添加select count
5. 根据不同的语法,替换成不同的内容。
以下是第0部分的实现:
定义一个抽象类BaseRepository,将来根据不同的数据库再定义相应的Repository如MySqlRepository,SqlServerRepository,该类里包含两个抽象方法,一个是getDataSource(),即获取连接池的连接,该属性将由Spring注入进来。另外一个是getScriptParser(),返回不同数据库对应的Sql处理类,如MySqlScriptParser,SqlServerScriptParser,另外还有一个DaoUtil类,主要用于处理参数赋值,结果集映射等辅助工作。
public abstract class BaseRepository {
//获取数据库连接,由Spring注入进来
protected abstract DataSource getDataSource();
//获取Sql分析实例
protected abstract ScriptParser getScriptParser();
//辅助类
public abstract DaoUtil getDaoUtil();
//参数Sql即执行的一条Sql,对于Oracle只能是单条语句,对于MySql和SqlServer,可以是用分号分割开的多条语句,paras即Sql里用?表示的占位参数的值
protected DataSet executeRetDataSet(String sql, Object... paras) {
//DataSet为辅助类,包含多个DataTable,DataTable也是辅助类,包含一个名为rows的ArrayList<ArrayList<String, Object>>对象,是从C#借鉴过来的
DataSet result = new DataSet();
Connection conn = null;
Statement stmt = null;
try {
conn = DataSourceUtils.getConnection(this.getDataSource());
ResultSet rs = null;
if (paras.length == 0) {
stmt = conn.createStatement();
stmt.execute(sql);
} else {
//此处是根据反射,查看参数的类型,进行特殊处理,如boolean要对应成数据库里的1或者0,Date类型要调用Statement的setTimestamp方法
PreparedStatement pstmt = getDaoUtil().getCallableStatement(conn, sql, paras);
pstmt.execute();
stmt = pstmt;
}
do {
//处理结果集
if (stmt.getUpdateCount() == -1) {
rs = stmt.getResultSet();
if (rs != null) {
result.tables.add(getDaoUtil().mapResultSetToDataTable(rs));
rs.close();
}
}
} while (stmt.getMoreResults() || stmt.getUpdateCount() > -1); stmt.close();
} catch (Exception ex) {
throw new RuntimeException(ex);
} finally {
if (stmt != null) {
JdbcUtils.closeStatement(stmt);
}
if (conn != null) {
DataSourceUtils.releaseConnection(conn, this.getDataSource());
}
} return result;
}
protected void executeNoRet(String sql, Object... paras) {
Connection conn = null;
Statement stmt = null;
try {
conn = DataSourceUtils.getConnection(this.getDataSource());
if (paras.length == 0) {
stmt = conn.createStatement();
stmt.execute(sql);
} else {
PreparedStatement pstmt = getDaoUtil().getCallableStatement(conn, sql, paras);
pstmt.execute();
stmt = pstmt;
}
stmt.close();
} catch (Exception ex) {
throw new RuntimeException(ex);
} finally {
if (stmt != null) {
JdbcUtils.closeStatement(stmt);
}
if (conn != null) {
DataSourceUtils.releaseConnection(conn, this.getDataSource());
}
}
}
//该方法接收一个Sql数组,paras为命名参数,对于同一个参数,Sql里可能出现多次,但paras只需要出现一次就可以。ScriptParser会把Sql里以@开头的参数替换成问号,并且组装出数量顺序一致的用于执行语句的参数数组。
public DataSet executeQuery(ArrayList<String> scripts, Map<String, Object> paras) {
//TupleTow为辅助类,类似其他语言里的元组
TupleTwo<String, ArrayList<Object>> ret = getScriptParser().parseScripts(scripts, paras);
return executeRetDataSet(ret.getItem1(), ret.getItem2().toArray());
}
public <T> void save(T item) {
Class<T> cls = (Class<T>) item.getClass();
save(cls, item);
}
public <T> void save(Class<T> cls, T item) {
throw new NotImplementedException();
}
public <T> T get(Class<T> cls, int id) {
throw new NotImplementedException();
}
}
浙公网安备 33010602011771号