JDBC实用知识–2

我们知道如何通过JDBC连接并操作数据库,但是每次我们实现增删改查时都会有一大段重复代码,所以本文主要是用实例说明了如何优化那些代码。这只是初步的简单优化。后面我会持续更新的。

1、前言

DBHelp类

什么是DBHelp类

  • DBHelp类是使用JDBC的工具类,在java提供的类中有大量类似的工具类,那么我们也可以自己编写工具类来解决使用中遇到的问题。并且可以帮助我们更加深刻的理解java源代码中编写的机制。

为什么使用DBHelp类

  • 我们知道每次使用JDBC实现java和数据库的连接时,需要使用JDBC加载数据路驱动、获取数据库连接、执行insert
    update delete
    以及select语句才能实现对数据库的“增删改查”。所以就造成了代码的大量的重复,使用非常不方便。那么我们就可以写一个DBHelp类来解决这个问题,实现代码的通用性。一次编写处处可用。

如何实现DBHelp的编写

  • 我们需要使用泛型的知识来实现代码的通用。

DBHelp类代码

  • 把加载数据库驱动、数据库连接和执行“增删改查”语句分别用独立的方法来写,代码更加精炼。
  • insert、update、delete都是调用executeUpdate()方法所以可以写成一个方法,只需要调用时传入不同的sql语句即可实现
  • 执行select语句在接收查询结果集resultSet之后,对结果进行封装时,因为具体对象不同,所以我们要在类的外部来进行封装,用一个接口来实现。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

public class DbHelp {

    private static final String DRIVER = "com.mysql.jdbc.Driver";
    private static final String URL = "jdbc:mysql:///db_22";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "root";

    /**
     * 获取数据库连接
     * 
     * @return
     */
    public static Connection getConnection() {
        Connection conn = null;
        try {
            Class.forName(DRIVER);
            conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

    /**
     * 执行INSERT UPDATE DELETE语句
     */
    public static void executeUpdate(String sql, Object... params) {
        Connection conn = null;
        PreparedStatement stat = null;
        try {
            conn = getConnection();
            stat = conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                stat.setObject(i + 1, params[i]);
            }
            stat.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            close(stat, conn);
        }
    }

    /**
     * 查询数据库,结果为单个对象
     */
    public static <T> T queryForObject(String sql, RowMapper<T> rowMapper, Object... params) {

        Connection conn = null;
        PreparedStatement stat = null;
        ResultSet rs = null;

        T result = null;

        try {
            conn = getConnection();
            stat = conn.prepareStatement(sql);

            for (int i = 0; i < params.length; i++) {
                stat.setObject(i + 1, params[i]);
            }

            rs = stat.executeQuery();

            if (rs.next()) {
                result = rowMapper.mapRow(rs);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            close(rs, stat, conn);
        }

        return result;
    }

    /**
     * 查询数据库,结果为集合
     */
    public static <T> List<T> queryForList(String sql, RowMapper<T> rowMapper, Object... params) {

        Connection conn = null;
        PreparedStatement stat = null;
        ResultSet rs = null;

        List<T> result = new ArrayList<T>();

        try {
            conn = getConnection();
            stat = conn.prepareStatement(sql);

            for (int i = 0; i < params.length; i++) {
                stat.setObject(i + 1, params[i]);
            }

            rs = stat.executeQuery();

            while (rs.next()) {
                T obj = rowMapper.mapRow(rs);
                result.add(obj);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            close(rs, stat, conn);
        }

        return result;
    }

    public static void close(ResultSet rs, Statement stat, Connection conn) {
        try {
            if (rs != null && !rs.isClosed()) {
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            close(stat, conn);
        }
    }

    public static void close(Statement stat, Connection conn) {
        try {
            if (stat != null && !stat.isClosed()) {
                stat.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                if (conn != null && !conn.isClosed()) {
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

    }

}

RowMapper接口

  • 用RowMapper的具体实现类就能完成对不同的对象进行不同的封装的效果

import java.sql.ResultSet;
import java.sql.SQLException;

public interface RowMapper<T> {

    T mapRow(ResultSet rs) throws SQLException;

}

以Account类来举例

  • 在Account类中封装了name address tel id
    等属性,在实现RowMapper的时候需要对这几个属性进行封装

    public class AccountRowMapper implements RowMapper <Account>{

        @Override
        public Account maprow(ResultSet rs) throws SQLException {
            Account account = new Account();
            account.setName(rs.getString("name"));
            account.setAddress(rs.getString("address"));
            account.setTel(rs.getString("tel"));
            account.setId(rs.getInt("id"));
            return account;
        } 
    }

调用DBHelp的方法代码

只需两行代码即可完成查找所有选项的目的

    public List<Account> queryForList(){
        String sql = "select *from T_account";
         return DBHelp.FindAll(sql, new AccountRowMapper());

实现增加删除修改的时候也是如此,以增加为例

public void save(Account account){

        String sql = "insert into t_account(name,address,tel) values(?,?,?)";
        DBHelp.ExecuteUpdate(sql, account.getName(), account.getAddress(),account.getTel());
    }

你要是忍不急想看主要代码,前往第5步工具类。

    玩过Java
web的人应该都接触过JDBC,正是有了它,Java程序才能轻松地访问数据库。JDBC很多人都会,但是为什么我还要写它呢?我曾经一度用烂了JDBC,一度认为JDBC不过如此,后来,我对面向对象的理解渐渐深入,慢慢地学会了如何抽象JDBC代码,再后来,我遇到了commons-dbutils这个轻量级工具包,发现这个工具包也是对JDBC代码的抽象,而且比我写的代码更加优化。在这个过程中,我体会到了抽象的魅力,我也希望通过这篇文章,把我的体会分享出来。

create table account( id int primary key auto_increment, name varchar, money float);character set utf8 collate utf8_general_ci;insert into account(name,money) values('aaa',1000);insert into account(name,money) values('bbb',1000);insert into account(name,money) values('ccc',1000);

  
文章大致按一定的逻辑进行:JDBC如何使用—–这样使用有什么问题——如何改进—–分析commons-dbutils的原理

在这个阶段的学习里,此配置文件可以当做模板来使用,并且里面有很详细的注释。

 

dbcpConfig.properties

2、JDBC如何使用

#连接设置driver=com.mysql.jdbc.Driverurl=jdbc:mysql://localhost:3306/testbatchusername=rootpassword=root#初始化连接initialSize=10#最大连接数量maxActive=50#<!-- 最大空闲连接 -->maxIdle=20#<!-- 最小空闲连接 -->minIdle=5#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000=60秒 -->maxWait=60000#JDBC驱动建立连接时附带的连接属性的格式必须为这样:[属性名=property;]#注意:"user"与"password"两个属性会被明确传递,因此这里不需要包含它们connectionProperties=useUnicode=true;characterEncoding=utf8#指定由连接池所创建的连接的自动提交(auto-commit)状态defaultAutoCommit=true#如果没有设置该值,则"setReadOnly"方法将不被调用。(某些驱动并不支持只读模式,如:Infomix)defaultReadOnly#driver default 指定由连接池所创建连接的事务级别(TransactionIsolation)#可用值:NONE,READ_UNCOMMITTED,READ_COMMITTED,REPEATABLE_READ,SERIALIZABLEdefalutTransactionIsolation=READ_COMMITTED

package cn.itcast.utils;import org.apache.commons.dbcp.BasicDataSourceFactory;import javax.sql.DataSource;import java.io.InputStream;import java.sql.Connection;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;import java.util.Properties;/** * Created by yvettee on 2017/9/30. */public class JdbcUtils_DBCP { private static DataSource ds = null; static { try { InputStream in = JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcpConfig.properties"); Properties prop = new Properties(); prop.load; BasicDataSourceFactory factory = new BasicDataSourceFactory(); ds = factory.createDataSource; } catch (Exception e) { throw new ExceptionInInitializerError; } } public static Connection getConnection() throws SQLException { return ds.getConnection(); } public static void release(Connection conn, Statement st, ResultSet rs) { if (rs != null) { try { rs.close(); //throw new } catch (Exception e) { e.printStackTrace(); } rs = null; } if (st != null) { try { st.close(); } catch (Exception e) { e.printStackTrace(); } st = null; } if (conn != null) { try { conn.close(); } catch (Exception e) { e.printStackTrace(); } } }}

package cn.itcast.domain;/** * Created by yvettee on 2017/9/30. */public class Account { private int id; private String name; private double money; public int getId() { return id; } public void setId { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getMoney() { return money; } public void setMoney(double price) { this.money = money; } @Override public String toString() { return "Account{" + ", name='" + name + ''' + ", money=" + money + '}'; }}

    这一小节通过一个例子来说明JDBC如何使用。

JdbcUtils

    我们大致可以讲JDBC的整个操作流程分为4步:

这个类里面就主要是对增删改查代码的优化了。增删改比较简单,用update一个方法就可以解决,主要就是查询比较复杂。因为查询时根据查询语句的不同,返回的结果集就不一样,不知道怎么处理,但是用户知道。在做开发的过程中,拿到结果集了,不知道怎么处理,就向用户暴露一个接口,你实现接口我这边就调用接口处理。

    1、获取数据库连接

package cn.itcast.utils;import java.sql.*;/** * Created by yvettee on 2017/9/30. */public class JdbcUtils { //add update delete // sql="insert into account(id,name,money) values Object[]{1,"a",100}" public static void update(String sql, Object params[]) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = JdbcUtils_DBCP.getConnection(); ps = conn.prepareStatement; //Object params[]替换sql里面的参数 for (int i = 0; i < params.length; i++) { ps.setObject(i + 1, params[i]); } ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } finally { JdbcUtils_DBCP.release(conn, ps, rs); } } //查找的优化是最难的 public static Object query(String sql, Object params[], ResultSetHandler handler) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = JdbcUtils_DBCP.getConnection(); ps = conn.prepareStatement; //Object params[]替换sql里面的参数 for (int i = 0; i < params.length; i++) { ps.setObject(i + 1, params[i]); } //不知道sql语句是怎样的,也就不知道怎么处理rs,但是用户知道怎么处理 rs = ps.executeQuery(); return handler.handler; } catch (SQLException e) { e.printStackTrace(); } finally { JdbcUtils_DBCP.release(conn, ps, rs); } return null; }}

    2、创建statement

ResultSetHandler接口

    3、执行sql语句并处理返回结果

package cn.itcast.utils;import java.sql.ResultSet;/** * Created by yvettee on 2017/10/2. *///暴露一个处理结果集的接口public interface ResultSetHandler { //返回多个值,Object类型 public Object handler(ResultSet rs);}

    4、释放不需要的资源

BeanHandlerImpl对接口进行处理

下面是一个小例子(省略了try-catch代码):

package cn.itcast.utils;import java.lang.reflect.Field;import java.sql.ResultSet;import java.sql.ResultSetMetaData;/** * Created by yvettee on 2017/10/2. */public class BeanHandlerImpl implements ResultSetHandler { private Class clazz; public BeanHandlerImpl(Class clazz) { this.clazz = clazz; } @Override public Object handler(ResultSet rs) { try { if (!rs.next { return null; } //创建出封装结果集的bean Object bean = clazz.newInstance(); //得到结果集的元数据,以获取结果集的信息 ResultSetMetaData meta = rs.getMetaData(); int count = meta.getColumnCount(); for (int i = 0; i < count; i++) { String name = meta.getColumnName;//获取到结果集每列的列名 Object value = rs.getObject; //将获取到的值封装到与bean上相对应的属性上去(反射出bean上与列名相应的属性) Field f = bean.getClass().getDeclaredField; f.setAccessible; f.set(bean, value); } return bean; } catch (Exception e) { throw new RuntimeException; } }}

图片 1

AccountDao

String username="root";
String password="123";
String url="jdbc:mysql://localhost/test";
Connection con=null;
Statement st=null;
ResultSet rs=null;

//1、获取连接
Class.forName("com.mysql.jdbc.Driver");         
con=DriverManager.getConnection(url,username,password);

//2、创建statement
String sql="select * from test_user";
st=con.createStatement();

//3、执行sql语句并处理返回结果
rs=st.executeQuery(sql);
while(rs.next())
 {
    //对结果进行处理
  }

//4、释放资源
rs.close();
st.close();
con.close();
package cn.itcast.dao.impl;import cn.itcast.domain.Account;/** * Created by yvettee on 2017/10/1. */public interface AccountDao { void add(Account account); void delete; void update(Account account); Account find;}

图片 2

AccountDaoImpl

以上的例子是查询的一种用法,除了用Statement外,还可以用PreparedStatement,后者是前者的子类,在前者的基础上增加了预编译和防止sql注入的功能。另外,查询和增删改是不同的用法,查询会返回ResultSet而增删改不会。

package cn.itcast.dao.impl;import cn.itcast.domain.Account;import cn.itcast.utils.BeanHandlerImpl;import cn.itcast.utils.JdbcUtils;/** * Created by yvettee on 2017/9/30. */public class AccountDaoImpl implements AccountDao { @Override public void add(Account account) { String sql = "insert into account(name,money) values"; Object params[] = {account.getName(), account.getMoney()}; JdbcUtils.update(sql, params); } @Override public void delete { String sql = "delete from account where id=?"; Object params[] = {id}; JdbcUtils.update(sql, params); } @Override public void update(Account account) { String sql = "update account set name=?,money=? where id=?"; Object params[] = {account.getName(), account.getMoney(), account.getId()}; JdbcUtils.update(sql, params); } @Override public Account find { String sql = "select * from account where id=?"; Object params[] = {id}; return JdbcUtils.query(sql,params,new BeanHandlerImpl(Account.class)); }}

package cn.itcast.test;import cn.itcast.dao.impl.AccountDao;import cn.itcast.dao.impl.AccountDaoImpl;import cn.itcast.domain.Account;import org.junit.Test;/** * Created by yvettee on 2017/10/1. */public class TestAccount { @Test public void testAdd() { Account a = new Account(); a.setName; a.setMoney; AccountDao dao = new AccountDaoImpl(); dao.add; System.out.println; } @Test public void testDelete() { AccountDao dao = new AccountDaoImpl(); dao.delete; } @Test public void testUpdate() { Account a = new Account(); a.setName; a.setMoney; a.setId; AccountDao dao = new AccountDaoImpl(); dao.update; } @Test public void testQuery() { AccountDao dao = new AccountDaoImpl(); dao.find; System.out.println(dao.find; }}

 

本文的源代码:

3、这样写代码有什么问题

对之前写的客户关系管理案例里面的代码进行小小的优化。源代码上篇:编写自己的JDBC框架之元数据

     
3.1、这样写代码会造成大量重复劳动,比如获取连接,如果每个执行sql的方法都要写一遍相同的代码,那么这样的重复代码将充斥整个DAO层。

      3.2、这样的代码可读性比较差,几十行代码真正和业务相关的其实就几行

     
3.3、大量重复代码会造成一个问题,那就是可维护性变差,一旦某个常量改变了,那么就需要把每个方法都改一遍

     
3.4、数据库连接是重量级资源,每调用一次方法都去创建一个连接,性能会存在瓶颈

 

4、如何改进

     
针对前面的问题中的1、2、3,改进的方法就是抽象,把可重用的代码抽象出去,单独组成一个模块,模块与模块之间实现解耦。由于整个JDBC操作流程分为4步,因此可以从这4步中下手去抽象。

     4.1、获取数据库连接

      
我当时的解决方案是一次初始化很多连接放入list,然后用的时候取,现在的通用方法就是连接池,比如DBCP、C3P0等等。有兴趣的人可以去看看它们的源代码,看看是如何实现的

    4.2、创建statement

      
我当时使用PreparedStatement进行处理,因为PreparedStatement会缓存已经编译过的sql

   4.3、执行sql语句并处理返回结果

     这块可以使用反射,将得到的结果封装成Java bean对象

   4.4、释放资源

    
使用动态代理,改变connection的close方法的行为,将connection放回连接池

 

5、commons-dbutils的原理

    
虽然我做出了改进,但距离真正的解耦还差得远,而commons-dbutils作为commons开源项目组中的一个成员,在这方面做得还算不错,通过阅读它的源代码,可以学习如何抽象和解耦JDBC的操作流程。

    5.1、整体结构

    先看一下它有哪些类:

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*
Website