java dao层封装实例_JavaEE中的MVC(一)Dao层彻底封装

本文介绍了如何对Java DAO层进行优化和封装,通过使用PreparedStatement实现增删改操作的通用方法,并设计了ResultSetParser接口实现查询结果的回调处理,从而简化查询代码。此外,还展示了如何通过反射机制和注解实现更灵活的DAO层封装。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

注:这是一个“重复造轮子”的过程,本文简单地实现了一个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 {

/**

* 处理结果集

*

* @param rs

* ResultSet

* @return List

*/

List parse(ResultSet rs);

/**

* 处理结果集

*

* @param rs

* ResultSet

* @return Object

*/

Object simpleParse(ResultSet rs);

}

Parser方法回调实现类

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

public abstract class Parser implements ResultSetParser {

@Override

public List parse(ResultSet resultSet) {

return null;

}

@Override

public Object simpleParse(ResultSet resultSet) {

return null;

}

}

代码封装

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

protected List executeQuerys(ResultSetParser 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 {

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

*/

protected List executeQuery(ResultSetParser 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

*/

protected Object executeSimpleQuery(ResultSetParser 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 {

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;

//方法补齐...

数据库对应表

a82edb09a801510e070d0537275242e8.png

Dao实现

/**

* 利用反射机制设计Dao

*

* @author CSS 2016/12/1

* @version 1.0

*

*/

public class EasyDao extends DBHelper {

/**

* 分页查询

* @param begin 开始位置

* @param count 取多少行记录

* @return

*/

public List getList(Class 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() {

@Override

public List parse(ResultSet resultSet) {

return fillArrayList(cl, resultSet);

}

}, sql.toString(), begin, count);

}

protected List fillArrayList(Class clazz, ResultSet resultSet) {

List list = new ArrayList();

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 extends DBHelper {

/**

* 分页查询

*

* @param begin

* 开始位置

* @param count

* 取多少行记录

* @return

*/

public List getList(Class 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() {

@Override

public List parse(ResultSet resultSet) {

return fillArrayList(cl, resultSet);

}

}, sql.toString(), begin, count);

}

protected List fillArrayList(Class clazz, ResultSet resultSet) {

List list = new ArrayList();

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 accountDao=new EasyDao2<>();

List 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

root

jdbc:mysql://192.168.28.217:3307/medicine?useUnicode=true&characterEncoding=UTF-8

com.mysql.jdbc.Driver

40

20

5

30

0

0

3

3

1000

3

10

5

1000

Test

true

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值