JDBC简略笔记(二)

141 阅读3分钟

声明

我自己写博客更多的是为了让自己尝试总结和方便之后复习,有错误的地方并非有意误导,而是实力太弱。如果读者发现有错误了,可以进行评论,我好进行思考改正。非常感谢。

本篇博客继续总结JDBC剩下的内容:数据库的事务 | DAO及其子类 | 数据库连接池 | DBUtils实现CRUD操作 感兴趣的可以去看尚硅谷的视频

1、数据库的事务

事务:指一组逻辑操作单元(一个或多个DML操作),使数据从一种状态转变到另一种状态。 当在一个事务中执行多个操作时,要么所有的操作都被提交(commit),即这些修改就永久的保存下来;要么数据库管理系统将放弃所有的所有操作,整个事务回滚(rollback)到最初的状态

代码体现

  • 取消数据的自动提交:conn.setAutoCommit(false);
  • 手动设置提交的位置(即事务的范围):conn.commit();
  • 回滚数据:conn.rollback();
@Test
    public void testUpdateWithTx() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            
            //1.取消数据的自动提交
            conn.setAutoCommit(false);
			
            String sql1 = "update user_table set balance = balance - 100 where user = ?";
            update(conn,sql1, "AA");			    
			
            String sql2 = "update user_table set balance = balance + 100 where user = ?";
            update(conn,sql2, "BB");			
		
            //2.提交数据
            conn.commit();
			
        } catch (Exception e) {
            e.printStackTrace();
            //3.回滚数据
            try {
                conn.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        }finally{
            //修改其为自动提交数据
            //主要针对于使用数据库连接池的使用
            try {
                conn.setAutoCommit(true);
            } catch (SQLException e) {
                e.printStackTrace();
            }
			
            JDBCUtils.closeResource(conn, null);
        }
		
    }

问题:既然一个事务包含了多个DML操作,那为了内存的考虑,最合理的就是建立一个connection就可以了。因此在增删改查的方法体中不需要新建连接,而应该在调用的时候connection作为参数传入。

//通用的增删改操作(考虑上事务)
    public int update(Connection conn,String sql, Object... args) {// sql中占位符的个数与可变形参的长度相同!
        PreparedStatement ps = null;
        try {
            // 1.预编译sql语句,返回PreparedStatement的实例
            ps = conn.prepareStatement(sql);
            // 2.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);// 小心参数声明错误!!
            }
            // 3.执行
            return ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 4.资源的关闭
            JDBCUtils.closeResource(null, ps);
        }
        return 0;
    }
//通用的查询操作,用于返回数据表中的一条记录(考虑上事务)
    public <T> T getInstance(Connection conn,Class<T> clazz,String sql, Object... args) {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {			
            ps = conn.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
            rs = ps.executeQuery();
            // 获取结果集的元数据 :ResultSetMetaData
            ResultSetMetaData rsmd = rs.getMetaData();
            // 通过ResultSetMetaData获取结果集中的列数
            int columnCount = rsmd.getColumnCount();
            if (rs.next()) {
                T t = clazz.newInstance();
                // 处理结果集一行数据中的每一个列
                for (int i = 0; i < columnCount; i++) {
                    // 获取列值
                    Object columValue = rs.getObject(i + 1);
                    // 获取每个列的列名
                    // String columnName = rsmd.getColumnName(i + 1);
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    // 给t对象指定的columnName属性,赋值为columValue:通过反射
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t, columValue);
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null, ps, rs);
        }
        return null;
    }

DAO及其子类

我在学习这个的过程中觉得很有意思,规范编写代码真的很巧妙,但是在这里就不赘述了,因为课程的代码也是CRUD功能进行封装的,实际使用是不需要自己写的,可以用其他的框架。下一节就会介绍。

数据库连接池(重点)

连接池的好处其实很直观。就是先事先准备一定数量的连接放在连接池中,要用的就去取,不用就放回。这样就不需要频繁的去创建和销毁了。

实现的方式:

image.png

DBUtils实现CRUD操作

导入jar包

image.png

使用现成的jar中的QueryRunner测试增、删、改的操作

    @Test
    public void testInsert() {
        Connection conn = null;
        try {
            QueryRunner runner = new QueryRunner();
            conn = JDBCUtils.getConnection();
            String sql = "insert into customers(name,email,birth)values(?,?,?)";
            int insertCount = runner.update(conn, sql, "蔡徐坤","caixukun@126.com","1997-09-08");
            System.out.println("添加了" + insertCount + "条记录");
        } catch (SQLException e) {
            e.printStackTrace();
        }finally{			
            JDBCUtils.closeResource(conn, null);
        }		
    }

使用现成的jar中的QueryRunner测试查询的操作

//测试查询
/*
 * BeanHander:是ResultSetHandler接口的实现类,用于封装表中的一条记录。
 */
@Test
    public void testQuery1(){
        Connection conn = null;
        try {
            QueryRunner runner = new QueryRunner();
            conn = JDBCUtils.getConnection();
            String sql = "select id,name,email,birth from customers where id = ?";
            BeanHandler<Customer> handler = new BeanHandler<>(Customer.class);
            Customer customer = runner.query(conn, sql, handler, 23);
            System.out.println(customer);
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
            JDBCUtils.closeResource(conn, null);			
        }		
    }

其实有多种类型的查询,一般的水平是不可能完全的自己编写,其实学会看和改就应该可以了。下面提供其他查询结果所用的方法,希望看到的时候能够知道是实现了什么功能,或者说知道用哪一种方法来实现,然后再去看手册。

/*
* BeanListHandler:是ResultSetHandler接口的实现类,用于封装表中的多条记录构成的集合。
*/
/*
 * MapHander:是ResultSetHandler接口的实现类,对应表中的一条记录。
 * 将字段及相应字段的值作为map中的key和value
 */
/*
 * MapListHander:是ResultSetHandler接口的实现类,对应表中的多条记录。
 * 将字段及相应字段的值作为map中的key和value。将这些map添加到List中
 */
/*
 * ScalarHandler:用于查询特殊值
 */

另外,使用dbutils.jar包中的DbUtils工具类实现连接等资源的关闭:

public static void closeResource1(Connection conn,Statement ps,ResultSet rs){
    DbUtils.closeQuietly(conn);
    DbUtils.closeQuietly(ps);
    DbUtils.closeQuietly(rs);
}

总结

JDBC的内容总结就是这样了,写完之后觉得写的很失败。最初的设想就是些一小部分就行了的,但是写着写着就不禁按照了课程的笔记走。最后挣扎一下,在这里补充:就JDBC的内容而言,实用点的话就只需要看具体数据库连接池是如何实现CRUD操作的就行了。其他博客总结的更加有针对性。Druid的操作