JavaEE中的MVC(一)Dao层彻底封装

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

posted on 2016-12-03 16:25  疯狂的妞妞  阅读(528)  评论(0编辑  收藏  举报

导航