写一个ORM框架的第一步(Apache Commons DbUtils)

新一次的内部提高开始了,若是您想写一个框架从Apache Commons DbUtils开始学习是一种不错的选择,咱们先学习应用这个小“框架”再把源代码理解,而后写一个属于本身的ORM框架不是梦。html

1、简介

DbUtils是Apache下commons工具集中的一个小工具,它主要是对JDBC封装的ORM小工具,简化了JDBC的操做。之因此把它称之为工具而不是框架,是由于它和其余的ORM框架仍是由很大的区别(例如Hibernate)。DbUtils并不支持所谓的聚合关联映射、缓存机制、实体状态的管理、延迟加载技术等等,它纯粹只是对JDBC的API进行封装。但也因为它的这种简单,所以性能高也是它的特色。DbUtils的源代码并很少,也很容易读得懂,很是适合于初学者阅读和学习。mysql

2、ORM概要

对象关系映射(Object Relational Mapping),简称ORM。网上有不少专业的解释,但对于初学者来讲这些专业的术语也许不太好理解。因此咱们仍是经过一些实际例子来讲明。git

在平常的开发中咱们常常用到实体或者DTO对象,这彷佛对每个程序员来讲都是再熟悉不过的了。但这些所谓的实体或者DTO对象按照领域驱动设计的说法,它们只有本身的属性,却没有属于本身的业务行为(get和set那不叫业务行为)。所以咱们把他们称之为贫血模型。那用这些贫血模型来作什么呢?没错,就是封装数据。咱们常常会将一些不一样类型的数据封装到这些对象中。程序员

Users user = new Users();
user.setUserName(“张三”);
user.setAge(20);

给对象属性赋完值之后,便把这个实体传递给Dao层执行保存操做。最终将数据持久化到数据库的某张表中。sql

public int persist(Users user) {
String sql = “INSERT INTO USERS_INFO(U_NAME, U_AGE) VALUES(?,?)”;
Connection conn = null;
PreparedStatment ps = null;
int row = 0;
try {
       conn = ConnUtil.getConnection();
       ps = conn.preparedStatment(sql);
       ps.setString(1, user.getUserName);
       ps.setInt(2, user.getAge());
       row = ps.executeUpdate();
} catch(SQLException e){
       e.printStackTrace();
} finally {
       ConnUtil.close(null, ps, conn);
}
return row;
}

在这个过程咱们发现一点,数据在Java中是以对象的形式存储,而最终持久化到数据库的时候是以关系型表格的形式存储,也就是说,咱们把一个对象化结构的数据映射到了关系型数据库中的这个过程,就是对象关系映射。反之,当咱们从关系型数据库中查询出的数据,又转换成一个对象模型的数据结构,这也是对象关系映射。数据库

public Users findUserById(int id) {
String sql = “SELECT * FROM USERS_INFO WHERE U_ID = ?”;
Connection conn = null;
PreparedStatment ps = null;
ResultSet rs = null;
Users user = null;

try {
       conn = ConnUtil.getConnection();
       ps = conn.preparedStatment(sql);
       ps.setString(1, id);
       rs = ps.executeQuery();

       if(rs.next()) {
            user = new Users();
            user.setId(rs.getInt(1));
            user.setUserName(rs.getString(2));
            user.setAge(rs.getInt(3));
       }
} catch(SQLException e){
       e.printStackTrace();
} finally {
       ConnUtil.close(rs, ps, conn);
}
return user;
}

所以,咱们能够将对象关系映射理解为它是一种对象模型和关系型数据库之间相互转换的过程。在实际开发中,咱们会遇到大量的ORM操做,然而你会发现,这种操做其实大部分都是重复劳动,频繁的给PreparedStatment设置参数,又或者是频繁的从ResultSet中读取数据保存到实体中,这些操做让咱们在开发中下降了效率。咱们可否将这些繁琐的操做封装起来,我给你一个实体,你会自动帮我保存到数据库。我告诉你一个对象的类型,你会自动将结果集中的数据封装到这个对象中返回给我。这样就大大简化的JDBC的操做,提升了开发效率。接下来咱们所学习的DbUtils就帮咱们完成了这些事情。apache

3、下载与安装

下载:数组

http://commons.apache.org/proper/commons-dbutils/download_dbutils.cgi缓存

安装:数据结构

教程中使用的是1.6的版本,下载的压缩包是 commons-dbutils-1.6-bin.zip。解压后将commons-dbutils-1.6.jar导入工程便可。

4、DML操做

首先,咱们在数据中建立USERS_INFO表。(mysql数据库)

CREATE TABLE USERS_INFO (
         ID INT PRIMARY KEY AUTO_INCREMENT,  -- 主键
         U_NAME VARCHAR(50) NOT NULL,  --姓名
         U_AGE  INT NOT NULL  --年龄
) CHARSET=UTF8 

这里咱们使用DBCP链接池做为数据源。DBCP也是commons工具集中一个小工具。简单点说,它主要用于监听和管理JDBC的Connection对象,达到链接复用的效果(链接池的原理及好处能够在JDBC教程的章节中进行查阅)。

DBCP链接池须要的jar文件:

  1. commons-dbcp2-2.1.1-bin.zip

下载地址: http://commons.apache.org/proper/commons-dbcp/download_dbcp.cgi

  1. commons-pool2-2.4.2-bin.zip

下载地址: http://commons.apache.org/proper/commons-pool/download_pool.cgi

  1. commons-logging-1.2-bin.zip

下载地址: http://commons.apache.org/proper/commons-logging/download_logging.cgi

解压后将commons-dbcp2-2.1.1.jar、commons-pool2-2.4.2.jar、commons-logging-1.2.jar这三个jar文件导入工程。

接下来编写一个DBCP链接池的工具类,用于获取DataSource

public class DBCPUtil {
private static Properties prop = new Properties();
private static DataSource dataSource;
/**
* 初始化链接池
*/
static {

        //驱动
        prop.setProperty("driverClassName", "com.mysql.jdbc.Driver");

        //链接url
        prop.setProperty("url", "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8");

        //用户名
        prop.setProperty("username", "root");

        //密码
        prop.setProperty("password", "root");

        //初始链接数
        prop.setProperty("initialSize", "5");

        //最大活动链接数
        prop.setProperty("maxTotal", "20");

        //最小空闲链接

        prop.setProperty("minIdle", "5");

        //最大空闲链接
        prop.setProperty("maxIdle", "10");

        //等待链接的最大超时时间(单位:毫秒)
        prop.setProperty("maxWaitMillis", "1000");

        //链接未使用时是否回收
        prop.setProperty("removeAbandonedOnMaintenance", "true");
        prop.setProperty("removeAbandonedOnBorrow", "true");
        try {
            dataSource = BasicDataSourceFactory.createDataSource(prop);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
/** * 获取DataSource * @return */ public static DataSource getDataSource(){ return dataSource; } }

4.一、QueryRunner

这个类用于发送执行SQL语句并返回相应的结果。其实现当中对Connection以及PreparedStatment接口的API进行了封装。QueryRunner有两种方式来管理链接,一种是在构建QueryRunner实例时经过构造方法传递一个数据源DataSource实例;另外一种则是在调用相应的操做方法,如query、update、batch等这些方法时传入一个Connection对象。这两种方式有什么区别呢?经过源码的阅读,咱们不难发现,其实对于DataSource的管理,在每次执行完相应操做后,DbUtils会自动关闭数据源的链接对象。而在调用相应的操做方法时传入的Connection对象,在使用完以后是须要咱们手动去关闭这个资源的。在如下全部的例子中,咱们都将使用DataSouce的方式进行操做。

4.二、Insert操做

/**
*  添加操做
* @param userName 姓名
* @param age 年龄
* @return int 影响的行数
* @throws SQLException
*/
    public int persist(String userName, int age) throws SQLException{
        String sql = "INSERT INTO USERS_INFO(U_NAME,U_AGE) VALUES(?,?)";
        //建立Query执行器,经过构造方法传入一个DataSource对象
        QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());
        // 执行update方法,方法的第一个和第二个参数分别是Connection对象和要执行的sql语句
        // 第三个参数开始是一个可变参数,分别是sql语句中所需的参数,对应上面语句中问号的顺序
        // 执行完成后会返回影响的行数
        return qr.update(sql, userName, age);
    }

4.三、Update操做

 /**

*  更新操做,用的是一样的方法,仅是sql语句的不一样
* @param userName 姓名
* @param age 年龄
* @param id 主键
* @return int 影响的行数
* @throws SQLException
*/

    public int update(String userName, int age, int id) throws SQLException{
        String sql = "UPDATE USERS_INFO SET U_NAME = ?, U_AGE = ? WHERE ID = ?";
        QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());
        return qr.update(sql, userName, age, id);
    }

4.四、Delete操做

 /**
*  删除操做,用的是一样的update方法,仅是sql语句的不一样
 * @param userName 姓名
 * @param id 主键
* @return int 影响的行数
* @throws SQLException
*/

    public int delete(int id) throws SQLException{
        String sql = "DELETE FROM USERS_INFO WHERE ID = ?";
        QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());
        return qr.update(sql, id);
    } 

4.五、批量操做

/**
* 批量操做
* @param params 批量执行SQL所需的参数,必须是一个二维数组
* @return int[] 影响的行数
* @throws SQLException
*/

    public int[] betch(Object[][] params) throws SQLException{
        String sql = "INSERT INTO USERS_INFO(U_NAME,U_AGE) VALUES(?,?)";
        QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());
        //使用batch方法,第三个参数是一个二维数组,数组中的每一个元素对应每次sql执行所需的参数
        //返回影响的行数的是一个int类型的数组
        return qr.batch(sql, params);
    }

5、DQL操做

5.一、ResultSetHandler接口

这个接口的核心做用是将查询结果进行封装(O/R Mapping)。它有许多不一样的实现类,每个实现类都将ResultSet中的结果封装成不一样类型的数据对象。以下图:

 

在ResultSetHandler众多的处理器实现类中主要分为两类,一类是处理单条结果集的,一类是处理多条结果集的。

单条数据处理器:BeanHandler、ArrayHandler、MapHandler、ScalarHandler

多条数据处理器:AbstractKeyedHandler(KeyedHandler、BeanMapHandler)、AbstractListHandler(ColumnListHandler、ArrayListHandler、MapListHandler)

5.一、BeanHandler

将单条查询结果封装为Bean对象

/**
* Users实体
*/

public class Users {

    private String userName;
    private int age;
    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

使用BeanHandler查询单条记录:

/**
* 使用BeanHandler查询单条记录
* @param id 主键
* @return  Users
*/

public Users findUserById(int uid) throws SQLException{
        //当表的列名和实体的属性名不一致时,在sql中使用as关键字给当前列指定别名,
        //别名和实体的属性名对应便可
        String sql = "SELECT U.U_NAME AS userName, U.U_AGE AS age FROM USERS_INFO U WHERE U.ID = ?";

        //建立QueryRunner实例
        QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());

        //使用BeanHandler类,泛型参数指定实体名称。构造方法指定实体的Class对象
        BeanHandler<Users> handler = new BeanHandler<>(Users.class);

        //调用query方法执行查询,该方法的参数一和参数二为链接对象和sql语句,
        //参数三为ResultSetHandler接口的实现类对象,这里是BeanHandler,
        //方法的第四个参数为可变参数,是sql查询时所需的条件参数
        //返回值则是一个封装好的实体对象
        Users user = qr.query(sql, handler, uid);

        return user;
}

将多条查询结果封装为List集合,集合中的每一个元素都是一个Bean对象

/**
* 使用BeanListHandler查询多条记录
* @return  List<Users>
*/

public List<Users> findUsers() throws SQLException{
        //当表的列名和实体的属性名不一致时,在sql中使用as关键字给当前列指定别名,
        //别名和实体的属性名对应便可
        String sql = "SELECT U.U_NAME AS userName, U.U_AGE AS age FROM USERS_INFO U";

        //建立QueryRunner实例
        QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());

        //使用BeanListHandler类
        BeanListHandler<Users> handler = new BeanListHandler<>(Users.class);

        //一样调用query方法执行查询,返回值则是一个List对象,List的泛型参数为实体类型
        List<Users> list = qr.query(sql, handler);

        return list;
    }

5.三、ArrayHandler 

将单条查询结果封装为一个Object数组

/**
* 使用ArrayHandler查询单条记录
* @param id 主键
* @return  Object []
*/

    public Object[] findUserById(int uid) throws SQLException{
        String sql = "SELECT U.U_NAME, U.U_AGE FROM USERS_INFO U WHERE U.ID = ?";
//建立QueryRunner实例 QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource()); //使用ArrayHandler类,因为ArrayHandler将结果集封装为Object数组,所以这个handler是不须要指定泛型的 ArrayHandler handler = new ArrayHandler(); //调用query方法执行查询,返回值则是一个Object数组 Object[] objects = qr.query(sql, handler, uid); return objects; }

5.四、ArrayListHandler

将多条查询结果封装为List集合,集合中的每一个元素都是一个Object数组

/**
* 使用ArrayListHandler查询多条记录
* @return  List<Object[]>
*/
    public List<Object[]> findUsers() throws SQLException{

        String sql = "SELECT U.U_NAME, U.U_AGE FROM USERS_INFO U";
//建立QueryRunner实例 QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource()); //使用ArrayListHandler类 ArrayListHandler handler = new ArrayListHandler(); //一样调用query方法执行查询,返回值则是一个List对象,List的泛型参数为Object数组类型 List<Object[]> list = qr.query(sql, handler); return list; }

5.五、MapHandler

将单条查询结果封装为一个Map对象, Key保存的是查询的列名,Value保存的是列的值

/**
* 使用MapHandler查询单条记录
* @param id 主键
* @return  Map<String, Object>
*/

    public Map<String, Object> findUserById(int id) throws SQLException{

        //当表的列名和实体的属性名不一致时,在sql中使用as关键字给当前列指定别名,
        //别名和实体的属性名对应便可
        String sql = "SELECT U.U_NAME, U.U_AGE FROM USERS_INFO U WHERE U.ID = ?";

        //建立QueryRunner实例
        QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());

        //使用MapHandler类,因为返回的是一个Map,所以这个handler也是是不须要指定泛型的
        MapHandler handler = new MapHandler();

        //调用query方法执行查询,返回值则是Map对象
        Map<String, Object> map = qr.query(sql, handler, uid);

        return map;
    }

5.六、MapListHandler

将多条查询结果封装为一个List集合,集合中的每一个元素都是一个Map对象

/**

* 使用MapListHandler查询多条记录
* @return  List<Map<String, Object>>
*/

    public List<Map<String, Object>> findUsers() throws SQLException{

        String sql = "SELECT U.U_NAME, U.U_AGE FROM USERS_INFO U";

        //建立QueryRunner实例
        QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());

        //使用MapListHandler类
        MapListHandler handler = new MapListHandler();

        //一样调用query方法执行查询,返回值则是一个List对象,List的泛型参数为Map类型
        List<Map<String, Object>> list = qr.query(sql, handler);

        return list;
    }

5.七、ScalarHandler

将单条查询结果中的某一列转换为指定的类型

/**
* 使用ScalarHandler查单条询记录中某一列
* @param id 主键
* @return String
*/

    public String findUserNameById(int id) throws SQLException{

        String sql = "SELECT U.U_NAME, U.U_AGE FROM USERS_INFO U WHERE U.ID = ?";

        //建立QueryRunner实例
        QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());

        //使用ScalarHandler类,泛型参数指定要返回的数据类型,构造方法指定查询结果中的某一列的下标
        ScalarHandler<String> handler = new ScalarHandler<>(1);

        //调用query方法执行查询,返回值则是String类型
        String userName = qr.query(sql, handler, id);

        return userName;

}

5.八、ColumnListHandler

将多条查询结果中的某一列封装为List集合

/**
* 使用ColumnListHandler查单多询记录中某一列
* @return  List<String>
*/

    public List<String> findUserNames() throws SQLException{

        String sql = "SELECT U.U_NAME, U.U_AGE FROM USERS_INFO U";

        //建立QueryRunner实例

        QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());

        //使用ColumnListHandler类, 泛型参数指定要返回的数据类型,构造方法指定查询结果中的某一列的下标

        ColumnListHandler<String> handler = new ColumnListHandler<>(1);

        //一样调用query方法执行查询,返回值则是一个List对象,List的泛型参数指定为查询结果转换的类型

        List<String> list = qr.query(sql, handler);

        return list;

    } 

5.九、KeyedHandler

将多条查询结果转换为Map,并将某列保存为Key,而Value则与MapHandler的查询结果同样,封装的是一个Map集合

/**
* 使用KeyedHandler查询结果转换为Map,并将某列的值保存为Key
* @return  Map<Integer, Map<String, Object>>
*/

    public Map<Integer, Map<String, Object>> findUsers() throws SQLException{
        String sql = "SELECT * FROM USERS_INFO U";
//建立QueryRunner实例 QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource()); //使用KeyedHandler类,泛型参数指定key的类型,构造方法中的参数指定哪一列的值做为key保存 //构造方法的参数能够是查询结果中某列的下标,也能够是列的名称 //KeyedHandler<Integer> handler = new KeyedHandler<>("ID"); KeyedHandler<Integer> handler = new KeyedHandler<>(1);
//一样调用query方法执行查询,返回值则是一个Map集合,key为查询某列的值,value为封装当前行的Map对象 Map<Integer, Map<String, Object>> map = qr.query(sql, handler); return map; }

5.10. BeanMapHandler

将多条查询结果转换为Map,并将某列保存为Key,而Value则与BeanHandler的查询结果同样,封装的是一个Bean对象

/**
* 使用MapBeanHandler查询结果转换为Map,并将某列的值保存为Key
* @return  Map<Integer, Users>
*/

    public Map<Integer, Users> findUsers() throws SQLException{

        String sql = "SELECT U.ID, U.U_NAME AS userName, U.U_AGE AS age FROM USERS_INFO U";
        //建立QueryRunner实例
        QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());

        //使用KeyedHandler类,泛型第一个参数指定key的类型,第二个参数指定查询结果转换的Bean类型,
        //构造方法中的第一个参数指定Bean的Class对象,第二个参数指定将查询结果的哪一列的值做为key保存
        //构造方法的参数能够是查询结果中某列的下标,也能够是列的名称
        //BeanMapHandler<Integer, Users> handler = new BeanMapHandler<>(Users.class, "ID");
        BeanMapHandler<Integer, Users> handler = new BeanMapHandler<>(Users.class, 1);
//一样调用query方法执行查询,返回值是一个Map集合,key为查询某列的值,value为封装当前行的Bean对象 Map<Integer, Users> map = qr.query(sql, handler);
return map; }

6、做业

6.一、做业要求

1. 任意数据库,创建一个表student,字段有id,name(id是自增加的,name是char类型)
2. 利用DbUtils完成CRUD操做
3. insert操做要能获得返回的自增加值
4. Connection对象要能正确处理

6.二、提交内容

1. 整个项目的源代码打包发到个人qq或者直接把你的项目的git地址告诉我便可

6.三、提交时间

2017-11-2号 星期四 中午12:00前

7、资料下载与说明

7.一、参考

7.二、资料下载

 连接: https://pan.baidu.com/s/1mhK2d5Y 密码: d78g

7.三、说明

文章内容由“王亮”老师提供,内部学习时由“陈军”老师讲授

相关文章
相关标签/搜索