JavaEE中的MVC(一)Dao层彻底封装
注:这是一个“重复造轮子”的过程,本文简单地实现了一个ORM框架
最近Android工作实在难找,考虑是不是该转行做Java了,今天开始,花几天的事件,研究一下JavaEE各层优化。
本文介绍的是Dao的优化,目前,像是Hibernate、Mybatis等框架都属于ORM框架,ORM是关系映射的意思;
在我们使用这些框架的时候,我们都需要去写配置文件,类名对应于哪个表,成员变量对应于哪个列等等;
在这些框架工作的时候,要先读取这些配置文件,然后根据文件中的映射关系,帮我们动态地去拼接SQL,或者自动地将数据打包成JavaBean。
增删改方法封装
使用PreparedStatement执行一条Sql语句的流程如下:
- 首先,Sql语句通常会有这么几种情况:
①更新语句:UPDATE accounts SET pwd=? WHERE (id=?),
②插入语句:INSERT INTO accounts ( pwd, account, addTime) VALUES (?,?,?)
③删除语句:DELETE FROM accounts WHERE (id=?) - 有了这些Sql语句之后,我们会调用Connection.prepareStatement(sql)方法;
- 然后依次调用PreparedStatement的set方法;
- 最后执行executeUpdate()方法。
这个流程有几个共同的特点:
- 这几个查询语句的执行结果都可以使用Boolean值表示;
- 参数的设置,都是调用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&characterEncoding=UTF-8
</property>
<!-- <property name="jdbcUrl">jdbc:mysql://10.50.8.50:3307/medicine?useUnicode=true&characterEncoding=UTF-8&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层彻底封装的更多相关文章
- JavaEE中的MVC(五)定制Struts——Action跳转JSP
在JavaEE中的MVC(三)中,我在Servlet中引入了命令模式的使用,采用Xml配置的方式,实现了一个Servlet调用多个不同的Action类,但是还不能实现页面地跳转,这一篇博客从之前的代码 ...
- 关于dao层的封装和前端分页的结合(文章有点长,耐心点哦)
任何一个封装讲究的是,实用,多状态.Action: 任何一个Action继承分页有关参数类PageManage,自然考虑的到分页效果,我们必须定义下几个分页的参数.并根据这个参数进行查值. 然 ...
- 带分页功能的SSH整合,DAO层经典封装
任何一个封装讲究的是,使用,多状态.Action: 任何一个Action继承分页有关参数类PageManage,自然考虑的到分页效果,我们必须定义下几个分页的参数.并根据这个参数进行查值. 然 ...
- MVC 的dao层、service层和controller层
1.dao层 dao层主要做数据持久层的工作, 负责与数据库进行联络的一些任务都封装在此 ,dao层的设计 首先 是设计dao层的接口,然后在Spring的配置文件中定义此接口的实现类,然后就可以再模 ...
- 通过对DAO层的封装减少数据库操作的代码量
在学框架之前,写项目时总是要花大量的时间去写数据库操作层代码,这样会大大降低我们的效率,为了解决这个问题,我花了两天时间利用反射机制和泛型将DAO层进行了封装,这样我们只需要写sql语句,不需要再写 ...
- Java通过JDBC 进行Dao层的封装
前言 前面有一章节,我专门讲解了Java通过JDBC 进行MySQL数据库操作,这主要讲解了MySQL数据库的连接和简单的操作,但是在真正的Java项目中,我们要不断的和数据库打交道,为了提高数据库操 ...
- JavaEE中的MVC(二)Xml配置实现IOC控制反转
毕竟我的经验有限,这篇文章要是有什么谬误,欢迎留言并指出,我们可以一起讨论讨论. 我要讲的是IOC控制反转,然后我要拿它做一件什么事?两个字:"解耦",形象点就是:表明当前类中需要 ...
- JavaEE中的MVC(三)定制Struts——命令模式
注:本文并不讲解Struts框架,只研究这种思想的使用 JavaEE中的MVC(五)定制Struts--Action跳转Jsp 在讲这个之前,先给你们看一下我服务器1.0版本的截图,是不是很可笑,看起 ...
- JavaEE中的MVC(四)AOP代理
咱们来吹牛,JDK的动态代理在AOP(Aspect Oriented Programming,面向切面编程)中被称为AOP代理,而AOP是Spring框架中的重要组成部分. 代理模式 但是什么是代理模 ...
随机推荐
- 移动端效果之IndexList
写在前面 接着前面的移动端效果讲,这次讲解的的是IndexList的实现原理.效果如下: 代码请看这里:github 移动端效果之swiper 移动端效果之picker 移动端效果之cellSwipe ...
- win10 uwp 获得元素绝对坐标
有时候需要获得一个元素,相对窗口的坐标,在修改他的位置可以使用. 那么 UWP 如何获得元素坐标? 我提供了一个方法,可以获得元素的坐标. 首先需要获得元素,如果没有获得元素,那么如何得到他的坐标? ...
- Android SDK国内更新
恩,废话就不多说了,俩网址,mirrors.neusoft.edu.cn和ubuntu.buct.edu.cn,在这里感谢wecloud和北京化工大学,十分感谢提供方便之门
- Mapper 动态代理方式
Mapper接口开发方法只需要程序员编写Mapper接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法. Mapper接口开发 ...
- LINUX 笔记-find 命令常用用法
命令格式: find path -option [-print] [-exec -ok] -print:find 命令将匹配的文件输出到标准输出 -exec:find 命令对匹配的文件执行该参数所给出 ...
- shell脚本学习(一):shell脚本开发的基本规范和习惯
1.脚本第一行指定脚本解释器 #!/bin/bash 或 #!/bin/sh 2.脚本开头增加作者.脚本作用描述等信息 1 #!/bin/bash 2 #Author: iskylite 3 #Blo ...
- SAP开发快捷键
F1 帮助 F2 回车确认(在某些地方可用,比如ABAP) F3 返回 F4 选择输入项 F5 新增 F6 复制为... F7 全选 F8 选择 ...
- Path.Combine 合并两个路径字符串,会出现的问题
Path.Combine(path1,path2) 1.如果path2字符串,以 \ 或 / 开头,则直接返回 path2
- python web框架篇:views视图函数
Django请求的生命周期是怎样的? 简单地说,通过URL对应关系匹配 ->找到对应的函数(或者类)->返回字符串(或者读取Html之后返回渲染的字符串) 解剖起来如下: 1. 当用户在浏 ...
- 分析Array.apply(null, { length: 5 })
Array.apply(null, { length: 5 }) 和 Array(5)有什么不同 注意:ES5,apply函数的第二个参数除了可以是数组外,还可以是类数组对象 // 类转成真正的数组 ...