JDBC# JDBC的简单理解概述

Posted LRcoding

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDBC# JDBC的简单理解概述相关的知识,希望对你有一定的参考价值。

1. JDBC入门

1.1. 什么是JDBC

JDBC全称为:Java Data Base Connectivity,它是可以执行SQL语句的Java API

1.2. JDBC简单操作

  • 导入mysql或者Oracle驱动包
  • 加载驱动
  • 获取与数据库连接的对象Connetcion
  • 获取执行sql语句的statement对象
  • 执行sql语句
  • 关闭连接
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;

try {
	// 加载驱动有两种方式
    // 1.会导致驱动会注册两次,过度依赖于mysql的api,脱离的mysql的开发包,程序则无法编译
    // DriverManager.registerDriver(new com.mysql.jdbc.Driver());
    
    // 2.驱动只会加载一次,不需要依赖具体的驱动,灵活性高
    Class.forName("com.mysql.jdbc.Driver");

    // 获取与数据库连接的对象-Connetcion
    connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");

    // 获取执行sql语句的statement对象
    statement = connection.createStatement();

    // 执行sql语句,拿到结果集
    resultSet = statement.executeQuery("SELECT * FROM users");

    // 遍历结果集,得到数据
    while (resultSet.next()) {
        System.out.println(resultSet.getString(1));
        System.out.println(resultSet.getString(2));
    }

} catch (SQLException e) {
    e.printStackTrace();
} catch (ClassNotFoundException e) {
    e.printStackTrace();
} finally {
    /*
     * 关闭资源,后调用的先关闭
     * 关闭之前,要判断对象是否存在
     **/
    if (resultSet != null) {
        try {
            resultSet.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    if (statement != null) {
        try {
            statement.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    if (connection != null) {
        try {
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

1.3. Connection对象

客户端与数据库所有的交互都是通过Connection来完成的。

//创建向数据库发送sql的statement对象。 
	createStatement() 

//创建向数据库发送【预编译sql】的PrepareSatement对象。 
	prepareStatement(sql) 

//创建执行存储过程的callableStatement对象 
	prepareCall(sql) 

//设置事务自动提交 
	setAutoCommit(boolean autoCommit) 

//提交事务
	commit() 

//回滚事务 
	rollback()

1.4. Statement对象

Statement对象用于向数据库发送Sql语句,对数据库的增删改查都可以通过此对象发送sql语句完成。

//查询 
	executeQuery(String sql)

//增删改 
	executeUpdate(String sql) 

//任意sql语句都可以,但是目标不明确,很少用 
	execute(String sql)

//把多条的sql语句放进同一个批处理中 
	addBatch(String sql)

//向数据库发送一批sql语句执行 
	executeBatch()

1.5. ResultSet对象

ResultSet对象代表Sql语句的执行结果,当Statement对象执行executeQuery()时,会返回一个ResultSet对象

//获取任意类型的数据 
	getObject(String columnName) 

//获取指定类型的数据【各种类型,查看API】 
	getString(String columnName)

//对结果集进行滚动查看的方法 
    next()
    Previous() 
    absolute(int row)
    beforeFirst() 
    afterLast()

1.6. 封装一个工具类UtilsDemo

/*
 * 连接数据库的driver,url,username,password通过配置文件来配置,可以增加灵活性
 * 当我们需要切换数据库的时候,只需要在配置文件中改以上的信息即可
 **/
private static String driver = null;
private static String url = null;
private static String username = null;
private static String password = null;

static {
    try {
        //获取配置文件的读入流
        InputStream inputStream = UtilsDemo.class.getClassLoader().getResourceAsStream("db.properties");
        Properties properties = new Properties();
        properties.load(inputStream);

        //获取配置文件的信息
        driver = properties.getProperty("driver");
        url = properties.getProperty("url");
        username = properties.getProperty("username");
        password = properties.getProperty("password");

        //加载驱动类
        Class.forName(driver);
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

public static Connection getConnection() throws SQLException {
    return DriverManager.getConnection(url, username, password);
}

public static void release(Connection conn, Statement stmt, ResultSet res) {
    if (res != null) {
        try {
            res.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    if (stmt != null) {
        try {
            stmt.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    if (conn != null) {
        try {
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

2. 使用细节

2.1. PreparedStatement对象

PreparedStatement对象继承Statement对象,它比Statement对象更强大

  • 编译SQL语句时,如果SQL语句有变量

    • Statement对象需要使用分隔符来隔开,如果变量非常多,就会使SQL变得非常复杂。
      • Statement会频繁编译SQL
    • PreparedStatement可以使用占位符,简化sql的编写
      • PreparedStatement可对SQL进行预编译,预编译的SQL存储在PreparedStatement对象中
  • PreparedStatement防止SQL注入。

2.2. 批处理

当需要向数据库发送一批SQL语句执行时,应避免向数据库一条条发送执行,采用批处理以提升执行效率

PreparedStatement方式实现批处理

/*
 * PreparedStatement批处理
 *   优点:
 *       SQL语句预编译了
 *       对于同一种类型的SQL语句,不用编写很多条
 *   缺点:
 *       不能发送不同类型的SQL语句
 **/
Connection connection = UtilsDemo.getConnection();

String sql = "INSERT INTO test(id,name) VALUES (?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);

for (int i = 1; i <= 205; i++) {
    preparedStatement.setInt(1, i);
    preparedStatement.setString(2, (i + "lwclick"));

    // 添加到批处理中
    preparedStatement.addBatch();

    // 不是一次执行所有的,当凑够100条后,剩下的再执行一次批处理
    if (i / 2 == 100) {
        // 执行批处理
        preparedStatement.executeBatch();
        
        // 清空批处理【如果数据量太大,所有数据存入批处理,内存肯定溢出】
        preparedStatement.clearBatch();
    }
}

// 再次执行插入操作
preparedStatement.executeBatch();

//再清空
preparedStatement.clearBatch();

UtilsDemo.release(connection, preparedStatement, null);

2.3. 处理大文本和二进制数据

  • clob用于存储大文本
  • blob用于存储二进制数据

2.4. 获取到自动主键列的值

resultSet = preparedStatement.getGeneratedKeys();


3. 事务

public void test(){
	// 开启事务
    conn.setAutoCommit(false);	
    try{
        // 业务处理
        ......

        // 提交事务
    } catch(SQLException e) {
        try{
            // 回滚
            conn.rollback();
            conn.setAutoCommit(true);
        } catch (SQLException e1){
            e1.printStackTrace();
        }
    } 
}

3.1. savapoint

使用savepoint设置中间点。如果在某地方出错了,我们设置中间点,回滚到出错的地方之前即可

savepoint不会结束当前事务,普通提交和回滚都会结束当前事务的

3.2. 元数据

元数据其实就是数据库,表,列的定义信息

  • ParameterMetaData --参数的元数据
  • ResultSetMetaData --结果集的元数据
  • DataBaseMetaData --数据库的元数据

4. 数据库连接池

DBCP, C3P0, Druid

4.1. dbutils框架

dbutils它是对JDBC的简单封装,极大简化jdbc编码的工作量

QueryRunner类

该类简化了SQL查询,配合ResultSetHandler使用,可以完成大部分的数据库操作,重载了许多的查询,更新,批处理方法。大大减少了代码量

ResultSetHandler接口

该接口规范了对ResultSet的操作,要对结果集进行什么操作,传入ResultSetHandler接口的实现类即可。

/*
 * 使用DbUtils框架对数据库的CRUD
 * 批处理
 *
 **/
public class Test {
    @org.junit.Test
    public void add() throws SQLException {
        //创建出QueryRunner对象
        QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "INSERT INTO student (id,name) VALUES(?,?)";

        //我们发现query()方法有的需要传入Connection对象,有的不需要传入
        //区别:你传入Connection对象是需要你来销毁该Connection,你不传入,由程序帮你把Connection放回到连接池中
        queryRunner.update(sql, new Object[]{"100", "zhongfucheng"});
    }

    @org.junit.Test
    public void query()throws SQLException {
        //创建出QueryRunner对象
        QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "SELECT * FROM student";

        List list = (List) queryRunner.query(sql, new BeanListHandler(Student.class));
        System.out.println(list.size());

    }

    @org.junit.Test
    public void delete() throws SQLException {
        //创建出QueryRunner对象
        QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "DELETE FROM student WHERE id='100'";

        queryRunner.update(sql);
    }

    @org.junit.Test
    public void update() throws SQLException {
        //创建出QueryRunner对象
        QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "UPDATE student SET name=? WHERE id=?";

        queryRunner.update(sql, new Object[]{"zhongfuchengaaa", 1});
    }

    @org.junit.Test
    public void batch() throws SQLException {
        //创建出QueryRunner对象
        QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
        String sql = "INSERT INTO student (name,id) VALUES(?,?)";

        Object[][] objects = new Object[10][];
        for (int i = 0; i < 10; i++) {
            objects[i] = new Object[]{"aaa", i + 300};
        }
        queryRunner.batch(sql, objects);
    }

}

5. 分页

5.1. Oracle实现分页

/*
 * Oracle分页语法:
 *	 @lineSize---每页显示数据行数
 *	 @currentPage----当前所在页
 **/
	SELECT * FROM (
	    SELECT 列名, 列名, ROWNUM rn
	    FROM 表名
	    WHERE ROWNUM <= (currentPage * lineSize)) temp
	WHERE temp.rn > (currentPage - 1) * lineSize;


Oracle的分页依赖于ROWNUM这个伪列,ROWNUM主要作用就是产生行号。
分页原理: 
	1:子查询查出前n行数据,ROWNUM产生前N行的行号 
	2:使用子查询产生ROWNUM的行号,通过外部的筛选出想要的数据

5.2. MySql实现分页

/*
 * Mysql分页语法:
 * 	@start---偏移量,不设置就是从0开始【也就是(currentPage-1)*lineSize】
 * 	@length---长度,取多少行数据
 **/
	SELECT *
	FROM 表名
	LIMIT [START], length;

5.3. JDBC实现

需求

  1. 算出有多少页的数据,显示在页面上

  2. 根据页码,从数据库显示相对应的数据。

分析

  1. 算出有多少页数据这是非常简单的【在数据库中查询有多少条记录,你每页显示多少条记录,就可以算出有多少页数据了】

  2. 使用Mysql或Oracle的分页语法即可

通过上面分析,我们会发现需要用到4个变量

  • currentPage–当前页【由用户决定的】
  • totalRecord–总数据数【查询表可知】
  • lineSize–每页显示数据的数量【由我们开发人员决定】
  • pageCount–页数【totalRecord和lineSize决定】
//每页显示3条数据
int lineSize = 3;

//总记录数
int totalRecord = getTotalRecord();

//假设用户指定的是第2页
int currentPage = 2;

//一共有多少页
int pageCount = getPageCount(totalRecord, lineSize);

//使用什么数据库进行分页,记得要在JdbcUtils中改配置
List<Person> list = getPageData2(currentPage, lineSize);
    for (Person person : list) {
        System.out.println(person);
    }
}

//使用JDBC连接Mysql数据库实现分页
public static List<Person> getPageData(int currentPage, int lineSize) throws SQLException {
    //从哪个位置开始取数据
    int start = (currentPage - 1) * lineSize;

    QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
    String sql = "SELECT name,address FROM person LIMIT ?,?";

    List<Person> persons = (List<Person>) queryRunner.query(sql, new BeanListHandler(Person.class), new Object[]{start, lineSize});
    return persons;
}

//使用JDBC连接Oracle数据库实现分页
public static List<Person> getPageData2(int currentPage, int lineSize) throws SQLException {
    //从哪个位置开始取数据
    int start = (currentPage - 1) * lineSize;

    //读取前N条数据
    int end = currentPage * lineSize;

    QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
    String sql = "SELECT " +
        " name, " +
        " address " +
        "FROM ( " +
        " SELECT " +
        "  name, " +
        "  address , " +
        "  ROWNUM rn " +
        " FROM person " +
        " WHERE ROWNUM <= ? " +
        ")temp WHERE temp.rn>?";

    List<Person> persons = (List<Person>) queryRunner.query(sql, new BeanListHandler(Person.class), new Object[]{end, start});
    return persons;
}

public static int getPageCount(int totalRecord, int lineSize) {
    //简单算法
    //return (totalRecord - 1) / lineSize + 1;

    //此算法比较好理解,把数据代代进去就知道了。
    return totalRecord % lineSize == 0 ? (totalRecord / lineSize) : (totalRecord / lineSize) + 1;
}

public static int getTotalRecord() throws SQLException {
    //使用DbUtils框架查询数据库表中有多少条数据
    QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());
    String sql = "SELECT COUNT(*) FROM person";

    Object o = queryRunner.query(sql, new ScalarHandler());

    String ss = o.toString();
    int s = Integer.parseInt(ss);
    return s;
}


6. 面试题

6.1. JDBC操作数据库的步骤 ?

1.	注册数据库驱动。 
2.	建立数据库连接Connect。 
3.	创建一个Statement。 
4.	执行SQL语句。 
5.	处理结果集。 
6.	关闭数据库连接

6.2. JDBC中的Statement 和PreparedStatement的区别?

•	PreparedStatement是预编译的SQL语句,效率高于Statement。 
•	PreparedStatement支持 ? 操作符,相对于Statement更加灵活。 
•	PreparedStatement可以防止SQL注入,安全性高于Statement。
•	CallableStatement适用于执行存储过程。

6.3. JDBC中大数据量的分页解决方法?

最好的办法是利用sql语句进行分页,这样每次查询出的结果集中就只包含某页的数据内容。

6.4. 说说数据库连接池工作原理和实现方案?

  • 工作原理:

    • 服务器启动时会建立一定数量的池连接,并一直维持不少于此数目的池连接。
    • 客户端程序需要连接时,池驱动程序会返回一个未使用的池连接并将其表记为忙。
    • 如果当前没有空闲连接,池驱动程序就新建一定数量的连接,新建连接的数量有配置参数决定。
    • 当使用的池连接调用完成后,池驱动程序将此连接表记为空闲,其他调用就可以使用这个连接。
  • 实现方案:

    • 连接池使用集合来进行装载,返回的Connection是原始Connection的代理,代理Connection的close方法,当调用close方法时,不是真正关连接,而是把它代理的Connection对象放回到连接池中,等待下一次重复利用。

6.5. Java中如何进行事务的处理?

1.	事务是作为单个逻辑工作单元执行的一系列操作。
2.	一个逻辑工作单元必须有四个属性,称为原子性、一致性、隔离性和持久性 (ACID) 属性,只有这样才能成为一个事务

Connection类中提供了4个事务处理方法:
• setAutoCommit(Boolean autoCommit):
	设置是否自动提交事务,默认为自动提交,即为true,通过设置false禁止自动提交事务; 
• commit():
	提交事务; 
• rollback():
	回滚事务.
• savepoint:
	保存点, savepoint不会结束当前事务,普通提交和回滚都会结束当前事务的

6.6. 注意事项

  • url、password等信息不应该直接使用字符串“写死”,可以使用常量代替

  • catch中应该回滚事务,抛出RuntimeException也是回滚事务的一种方法

  • 关闭资源

6.7. 写出一段JDBC连接本机MySQL数据库的代码

Class.forName("com.mysql.jdbc.Driver");
String url="jdbc:mysql://localhost/test";
Stirng user='root';
String password='root';
Connection conn = DriverManager.getConnection(url,user,password);

6.8. JDBC是如何实现Java程序和JDBC驱动的松耦合的?

通过制定接口,数据库厂商来实现。

6.9. execute,executeQuery,executeUpdate的区别是什么?