注:这是一个“重复造轮子”的过程,本文简单地实现了一个ORM框架

最近Android工作实在难找,考虑是不是该转行做Java了,今天开始,花几天的事件,研究一下JavaEE各层优化。

本文介绍的是Dao的优化,目前,像是Hibernate、Mybatis等框架都属于ORM框架,ORM是关系映射的意思;

在我们使用这些框架的时候,我们都需要去写配置文件,类名对应于哪个表,成员变量对应于哪个列等等;

在这些框架工作的时候,要先读取这些配置文件,然后根据文件中的映射关系,帮我们动态地去拼接SQL,或者自动地将数据打包成JavaBean。

增删改方法封装

使用PreparedStatement执行一条Sql语句的流程如下:

  1. 首先,Sql语句通常会有这么几种情况:
    ①更新语句:UPDATE accounts SET pwd=? WHERE (id=?),
    ②插入语句:INSERT INTO accounts ( pwd, account, addTime) VALUES (?,?,?)
    ③删除语句:DELETE FROM accounts WHERE (id=?)
  2. 有了这些Sql语句之后,我们会调用Connection.prepareStatement(sql)方法;
  3. 然后依次调用PreparedStatement的set方法;
  4. 最后执行executeUpdate()方法。

这个流程有几个共同的特点:

  1. 这几个查询语句的执行结果都可以使用Boolean值表示;
  2. 参数的设置,都是调用PreparedStatement的set方法,查看API,可以看到PreparedStatement有一个setObject()方法,因为参数是Object,也就是说,PreparedStatement的set方法都可以使用setObject()替代;

代码封装

根据上面的说法,就可以实现下面这样的封装,一个能执行任何增删改Sql语句的方法:

    protected boolean executeUpdates(String sql, Object... params) throws SQLException {
pstmt = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
pstmt.setObject(i + 1, params[i]);
}
return pstmt.executeUpdate() > 0;
}

查询方法封装

查询语句之所以不同于其它方法,原因是有一个ResultSet需要返回,ResultSet是一个需要被关闭的对象,怎么处理ResultSet?

  • 思路一:对ResultSet进行二次封装(目前我已经实现,代码相对复杂,这里就不具体展开,有兴趣可以一起讨论);
  • 思路二:接口回调,或者方法回调。

这里就采用方法回调。

ResultSetParser接口设计

public interface ResultSetParser<T> {
/**
* 处理结果集
*
* @param rs
* ResultSet
* @return List<T>
*/
List<T> parse(ResultSet rs); /**
* 处理结果集
*
* @param rs
* ResultSet
* @return Object
*/
Object simpleParse(ResultSet rs);
}

Parser方法回调实现类

这个类实现了ResultSetParser接口,但是方法都没真正实现,只是写了空方法,由真正的子类去实现

public abstract class Parser<T> implements ResultSetParser<T> {
@Override
public List<T> parse(ResultSet resultSet) {
return null;
} @Override
public Object simpleParse(ResultSet resultSet) {
return null;
}
}

代码封装

于是,就有了下面这样的封装,一个能执行任何查询Sql语句的方法,其中参数ResultSetParser由调用者做具体的实现:

    protected List<T> executeQuerys(ResultSetParser<T> resultSetPaser, String sql, Object... params) throws Exception {
pstmt = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
pstmt.setObject(i + 1, params[i]);
}
rs = pstmt.executeQuery();
return resultSetPaser.parse(rs);
}

实现类BaseDao最终封装(源码)

实际取名是DBHelper,为什么不取名BaseDao,因为最初的思路,我是将其设计为工具类,并不是非得继承才可以使用。

public class DBHelper<T> {
private Connection conn = null;
private PreparedStatement pstmt = null;
private ResultSet rs = null;
// 开启事务标志
private boolean autoCommit = true; /**
* 核心方法,开启事务
*/
public void beginTransaction() {
autoCommit = false;
} /**
* 核心方法,提交事务
*/
public void commit() {
try {
conn.commit();
autoCommit = true;
} catch (SQLException e) {
e.printStackTrace();
}
} /**
* 核心方法,获取数据库连接
*/
private void begin() {
try {
conn = ConnectionPool.getConnection();
if (autoCommit)
return;
conn.setAutoCommit(false);
} catch (Exception e) {
e.printStackTrace();
}
} /**
* 核心方法:释放资源
*/
private void close(ResultSet resultSet, Statement statement, Connection connection) {
try {
if (resultSet != null)
resultSet.close();
if (statement != null)
statement.close();
if (connection != null)
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
} /**
* 核心方法:为PreparedStatement设置参数
*
* @param pstmt
* PreparedStatement
* @param params
* 参数
*/
private void setParams(PreparedStatement pstmt, Object... params) {
try {
for (int i = 0; i < params.length; i++) {
pstmt.setObject(i + 1, params[i]);
}
} catch (SQLException e) {
e.printStackTrace();
}
} /**
* 增加、删除、修改的统一方法
*
* @param sql
* SQL语句
* @param params
* 参数
*/
protected boolean executeUpdate(String sql, Object... params) {
begin();
try {
pstmt = conn.prepareStatement(sql);
setParams(pstmt, params);
return pstmt.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(rs, pstmt, conn);
}
return false;
} /**
* 查询的统一方法
*
* @param rsUtil
* 处理结果集的接口
* @param sql
* 语句
* @param params
* 参数
* @return List<T>
*/
protected List<T> executeQuery(ResultSetParser<T> rsUtil, String sql, Object... params) {
begin();
try {
pstmt = conn.prepareStatement(sql);
setParams(pstmt, params);
rs = pstmt.executeQuery();
return rsUtil.parse(rs);
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(rs, pstmt, conn);
}
return null;
} /**
* 查询的统一方法
*
* @param rsUtil
* 处理结果集的接口
* @param sql
* 语句
* @param params
* 参数
* @return List<T>
*/
protected Object executeSimpleQuery(ResultSetParser<T> resultSetPaser, String sql, Object... params) {
begin();
try {
pstmt = conn.prepareStatement(sql);
setParams(pstmt, params);
rs = pstmt.executeQuery();
return resultSetPaser.simpleParse(rs);
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(rs, pstmt, conn);
}
return null;
}
}

BaseDao投入实战

我们需要一个AccountsDao,只要去继承BaseDao就好了,假如我们有个添加一个Accounts对象的需求,代码变得异常地简单,如下所示,仅仅只需要两行。

public class AccountsDao extends DBHelper<Accounts> {
public void insert(Accounts accounts) {
String sql = "INSERT INTO `accounts` (`integral`, `pwd`, `account`, `addTime`, `login`, `money`, `isEnable`) VALUES (?,?,?,?,?,?,?)";
super.executeUpdate(sql, accounts.getIntegral(), accounts.getPassword(), accounts.getAccount(),
accounts.getAddTime(), accounts.getLogin(), accounts.getMoney(), accounts.getIsEnable());
}
}

利用反射机制设计万能Dao

其实看到上面这一串代码,可能还是略显蛋疼,假如说我们一张表有20列,这个时候去写一个Sql语句,那真的要疯了,你要写20个?号,如果有Where子句,还需要更多。

我的思路是采用反射机制来做,设计一个Javabean,他的类名和字段都和数据库的相匹配,利用反射拼出Sql语句。

因为算法的关系,这肯定会牺牲一定的查询效率,但是可以完成数据连接层的彻底封装。

注意:使用注解、反射、配置文件,都会浪费一定的时间去解析,因此,最好可以去考虑设计一个缓存域,用于缓存已经查询的数据,也可以考虑缓存反射生成的Sql语句。

测试用Javabean

public class Accounts {
private long id;
private long integral;
private String pwd;
private String account;
private Timestamp addTime;
private Timestamp login;
private int money;
private boolean isEnable;
//方法补齐...
}

数据库对应表

Dao实现

/**
* 利用反射机制设计Dao
*
* @author CSS 2016/12/1
* @version 1.0
*
*/
public class EasyDao<T> extends DBHelper<T> {
/**
* 分页查询
* @param begin 开始位置
* @param count 取多少行记录
* @return
*/
public List<T> getList(Class<T> cl, int begin, int count) {
StringBuffer sql = new StringBuffer(120);
sql.append("select * from ").append(cl.getSimpleName());
if (begin != -1)
sql.append(" LIMIT ?");
if (count != -1)
sql.append(",?");
System.out.println(sql.toString()); return executeQuery(new Parser<T>() {
@Override
public List<T> parse(ResultSet resultSet) {
return fillArrayList(cl, resultSet);
}
}, sql.toString(), begin, count);
} protected List<T> fillArrayList(Class<T> clazz, ResultSet resultSet) {
List<T> list = new ArrayList<T>();
try {
Field[] fields = clazz.getDeclaredFields();
resultSet.beforeFirst();
while (resultSet.next()) {
T t = (T) clazz.newInstance(); for (int i = 0; i < fields.length; i++) {
fields[i].setAccessible(true);
fields[i].set(t, resultSet.getObject(fields[i].getName()));
}
list.add(t);
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
}

在万能Dao中引入注解的使用

设计注解接口

/**
* 指明字段在数据库中对应的列名
* @author ChenSS
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
String value();
}

在Javabean中使用注解

public class Accounts {
@Column("id")
private long id;
@Column("integral")
private long integral;
@Column("pwd")
private String pwd;
@Column("account")
private String account;
@Column("addTime")
private Timestamp addTime;
@Column("login")
private Timestamp login;
@Column("money")
private int money;
@Column("isEnable")
private boolean isEnable;
}

Dao实现

曾经写过关于注解使用的文章,想使用参数注解设计,但是设计最终没完成,目前我依旧没能力去解决那些问题,这里换了个思路,使用字段注解。

/**
* 利用注解设计Dao,解决了反射硬性要求数据库字段与Javabean对应的问题,比直接用反射更加灵活
*
* @author CSS 2016/12/1
* @version 1.0
*
*/
public class EasyDao2<T> extends DBHelper<T> {
/**
* 分页查询
*
* @param begin
* 开始位置
* @param count
* 取多少行记录
* @return
*/
public List<T> getList(Class<T> cl, int begin, int count) {
StringBuffer sql = new StringBuffer(120);
sql.append("select * from ").append(cl.getSimpleName());
if (begin != -1)
sql.append(" LIMIT ?");
if (count != -1)
sql.append(",?");
System.out.println(sql.toString()); return executeQuery(new Parser<T>() {
@Override
public List<T> parse(ResultSet resultSet) {
return fillArrayList(cl, resultSet);
}
}, sql.toString(), begin, count);
} protected List<T> fillArrayList(Class<T> clazz, ResultSet resultSet) {
List<T> list = new ArrayList<T>();
try {
Field[] fields = clazz.getDeclaredFields();
resultSet.beforeFirst();
while (resultSet.next()) {
T t = (T) clazz.newInstance();
for (int i = 0; i < fields.length; i++) { //获取注解值
Column column = (Column) fields[i].getAnnotations()[0];
if (column == null)
continue;
fields[i].setAccessible(true);
fields[i].set(t, resultSet.getObject(column.value())); }
list.add(t);
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
}

测试类

public class Test {
public static void main(String[] args) {
EasyDao2<Accounts> accountDao=new EasyDao2<>();
List<Accounts> list=accountDao.getList(Accounts.class, 0, 1);
System.out.println(list.toString()); AccountsDao accountsDao=new AccountsDao();
accountsDao.insert(list.get(0));
}
}

C3P0连接池配置

首先你需要一个C3P0的Jar包,c3p0-config.xml放在src根目录下,ConnectionPool位置任意。

连接池Java代码

/**
* 数据库链接对象管理类
*
* @author CSS
* @version 1.0
*
*/
public class ConnectionPool {
private static ComboPooledDataSource dpds = null; private ConnectionPool() {
} static {
if (dpds == null)
createComboPooledDataSource();
} private synchronized static void createComboPooledDataSource() {
if (dpds == null)
dpds = new ComboPooledDataSource("mysql");
} public synchronized static Connection getConnection() {
try {
return dpds.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
} @Override
protected void finalize() throws Throwable {
if (dpds != null)
DataSources.destroy(dpds);
super.finalize();
}
}

c3p0-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<named-config name="mysql">
<!-- 配置数据库用户名 -->
<property name="user">root</property>
<!-- 配置数据库密码 -->
<property name="password"></property>
<!-- 配置数据库链接地址 -->
<property name="jdbcUrl">jdbc:mysql://192.168.28.217:3307/medicine?useUnicode=true&amp;characterEncoding=UTF-8
</property>
<!-- <property name="jdbcUrl">jdbc:mysql://10.50.8.50:3307/medicine?useUnicode=true&amp;characterEncoding=UTF-8&amp;useSSL=false
</property> --> <!-- 配置数据库驱动 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<!-- 数据库连接池一次性向数据库要多少个连接对象 -->
<property name="acquireIncrement">40</property>
<!-- 初始化连接数 -->
<property name="initialPoolSize">20</property>
<!-- 最小连接数 -->
<property name="minPoolSize">5</property>
<!--连接池中保留的最大连接数。Default: 15 -->
<property name="maxPoolSize">30</property>
<!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements 属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default:0 -->
<property name="maxStatements">0</property>
<!--maxStatementsPerConnection定义了连接池内单个连接所拥有的最大缓存statements数。Default: 0 -->
<property name="maxStatementsPerConnection">0</property>
<!--c3p0是异步操作的,缓慢的JDBC操作通过帮助进程完成。扩展这些操作可以有效的提升性能 通过多线程实现多个操作同时被执行。Default:3 -->
<property name="numHelperThreads">3</property>
<!--用户修改系统配置参数执行前最多等待300秒。Default: 300 -->
<property name="propertyCycle">3</property>
<!-- 获取连接超时设置 默认是一直等待单位毫秒 -->
<property name="checkoutTimeout">1000</property>
<!--每多少秒检查所有连接池中的空闲连接。Default: 0 -->
<property name="idleConnectionTestPeriod">3</property>
<!--最大空闲时间,多少秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
<property name="maxIdleTime">10</property>
<!--配置连接的生存时间,超过这个时间的连接将由连接池自动断开丢弃掉。当然正在使用的连接不会马上断开,而是等待它close再断开。配置为0的时候则不会对连接的生存时间进行限制。 -->
<property name="maxIdleTimeExcessConnections">5</property>
<!--两次连接中间隔时间,单位毫秒。Default: 1000 -->
<property name="acquireRetryDelay">1000</property>
<!--c3p0将建一张名为Test的空表,并使用其自带的查询语句进行测试。如果定义了这个参数那么属性preferredTestQuery将被忽略。你不能在这张Test表上进行任何操作,它将只供c3p0测试使用。Default:
null -->
<property name="automaticTestTable">Test</property>
<!-- 获取connnection时测试是否有效 -->
<property name="testConnectionOnCheckin">true</property>
</named-config>
</c3p0-config>

JavaEE中的MVC(一)Dao层彻底封装的更多相关文章

  1. JavaEE中的MVC(五)定制Struts——Action跳转JSP

    在JavaEE中的MVC(三)中,我在Servlet中引入了命令模式的使用,采用Xml配置的方式,实现了一个Servlet调用多个不同的Action类,但是还不能实现页面地跳转,这一篇博客从之前的代码 ...

  2. 关于dao层的封装和前端分页的结合(文章有点长,耐心点哦)

    任何一个封装讲究的是,实用,多状态.Action:     任何一个Action继承分页有关参数类PageManage,自然考虑的到分页效果,我们必须定义下几个分页的参数.并根据这个参数进行查值. 然 ...

  3. 带分页功能的SSH整合,DAO层经典封装

    任何一个封装讲究的是,使用,多状态.Action:     任何一个Action继承分页有关参数类PageManage,自然考虑的到分页效果,我们必须定义下几个分页的参数.并根据这个参数进行查值. 然 ...

  4. MVC 的dao层、service层和controller层

    1.dao层 dao层主要做数据持久层的工作, 负责与数据库进行联络的一些任务都封装在此 ,dao层的设计 首先 是设计dao层的接口,然后在Spring的配置文件中定义此接口的实现类,然后就可以再模 ...

  5. 通过对DAO层的封装减少数据库操作的代码量

     在学框架之前,写项目时总是要花大量的时间去写数据库操作层代码,这样会大大降低我们的效率,为了解决这个问题,我花了两天时间利用反射机制和泛型将DAO层进行了封装,这样我们只需要写sql语句,不需要再写 ...

  6. Java通过JDBC 进行Dao层的封装

    前言 前面有一章节,我专门讲解了Java通过JDBC 进行MySQL数据库操作,这主要讲解了MySQL数据库的连接和简单的操作,但是在真正的Java项目中,我们要不断的和数据库打交道,为了提高数据库操 ...

  7. JavaEE中的MVC(二)Xml配置实现IOC控制反转

    毕竟我的经验有限,这篇文章要是有什么谬误,欢迎留言并指出,我们可以一起讨论讨论. 我要讲的是IOC控制反转,然后我要拿它做一件什么事?两个字:"解耦",形象点就是:表明当前类中需要 ...

  8. JavaEE中的MVC(三)定制Struts——命令模式

    注:本文并不讲解Struts框架,只研究这种思想的使用 JavaEE中的MVC(五)定制Struts--Action跳转Jsp 在讲这个之前,先给你们看一下我服务器1.0版本的截图,是不是很可笑,看起 ...

  9. JavaEE中的MVC(四)AOP代理

    咱们来吹牛,JDK的动态代理在AOP(Aspect Oriented Programming,面向切面编程)中被称为AOP代理,而AOP是Spring框架中的重要组成部分. 代理模式 但是什么是代理模 ...

随机推荐

  1. string使用

    一.list和string转化 List转字符串,用逗号隔开 List<string> list = new List<string>();list.Add("a&q ...

  2. checkbox插件

    1.全选或者全不选 首先判断全选或全不选checkbox是否被选中. 如果被选中,则为每个选项checkbox设置obj.checked='checked'; 如果未被选中,则为每个选项checkbo ...

  3. nodejs里的module.exports和exports

    引 在node.js中我们可以使用module.exports和exports导出模块,设置导出函数.数组.变量等等 为什么可以用这两个模块? 或者直接问,node.js的模块功能是怎么实现的. 这样 ...

  4. HiveQL简单操作DDL

    hive-2.1.1 DDL操作 Create/Drop/Alter/Use Database 创建数据库 //官方指导 CREATE (DATABASE|SCHEMA) [IF NOT EXISTS ...

  5. python第五课——自定义线程池

    内容概要: 1.low版线程池 2.绝版线程池 1.low版线程池 设计思路:运用队列queue 将线程类名放入队列中,执行一个就拿一个出来 import queue import threading ...

  6. JS的简单用法

    JS的简单用法 参考:http://www.w3school.com.cn/js/js_switch.asp JavaScript 是网络的脚本语言 JavaScript 是可插入 HTML 页面的编 ...

  7. JAVA基础知识总结:八

    面向对象语言的三大特性;封装.继承.多态 一.面向对象语言特性之封装 1.什么是封装? 一个类中某些属性,如果不希望外界直接访问,我们可以将这个属性作为私有的,可以给外界暴露出来一个访问的方法 使用封 ...

  8. json_encode()中文不转码

    php使用json_encode()把数组转换为json的时候,总会把中文进行转码,转码后从json数据上我们无法看出其中的中文文字.php5.4以后,当json_encode()的第二个参数赋值为J ...

  9. LeetCode 461. Hamming Distance (汉明距离)

    The Hamming distance between two integers is the number of positions at which the corresponding bits ...

  10. instanceof 原理

    运行流程 function instance_of(L, R) {                               //L 表示左表达式,R 表示右表达式   var O = R.prot ...