JDBC学习笔记

目录

1:为什么要学习JDBC技术

2、JDBC技术概述与理解

3、JDBC使用步骤分析

3.1、注册驱动

3.2 、获取连接

3.3、创建发送sql语句对象

3.4、发送sql语句

3.5、结果集解析

3.6、资源关闭

4、Jdbc扩展提升

4.1、获取主键值

4.2、批量化插入操作

4.3、事务处理

5、Druid 连接池技术的使用

5.1、连接性能消耗问题

5.2、数据库连接池作用

5.3、连接池的建立

5.3.1、硬连接池建立方式

5.3.2、软连接池建立方式

5.4、工具类的封装与使用

5.4.1初步封装

5.4.2、非DQL语句封装

5.4.3、DQL语句封装

5.4.4、封装后的使用


1:为什么要学习JDBC技术

1、Java和数据库的必要纽带。

2、数据库层框架底层原理,原生态的jdbc技术对数据库操作会显得有些笨拙,所以我们会选择第三方数据库框架进行数据库操作,例mybatis、hibernate、springdatajpa,这些都是应用层的框架,底层原理都是对jdbc操作的封装。

2、JDBC技术概述与理解

总结: Jdbc是java连接数据库技术的统称 jdbc是由两部分组成:

一、是Java提供的规范(接口) ​ 二、是各个数据库厂商的实现驱动jar包! Jdbc技术是一种典型的面向接口编程!

3、JDBC使用步骤分析

1、注册驱动

2、获取连接

3、创建发送sql语句对象

4、发送sql语句,并获取返回结果

5、结果集解析

6、资源关闭

3.1、注册驱动

方式一:

DriverManager.registerDriver(new Driver());

方式二:

new Driver();

方式三:

Class.forName("com.mysql.cj.jdbc.Driver");

方式一相比于方式二和三,不好的地方在于注册了两次驱动,方式二三都只有一次驱动。

但是常用的是第三种方式,第二种方式过于固定化,相当于把代码写死了,更换数据库类型的时候还需要改代码。

第三种可以把字符串提取到外部的配置文件,后期只需要进行修改外部的配置文件即可。

3.2 、获取连接

Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/jdbc", "XXX", "XXX");

其中包含三个参数:

第一个(url):格式为 jdbc:数据库类型://主机ip地址:数据库端口/数据库名

第二个(账号):连接的数据库账号

第三个(密码):该库的密码

其中第一个参数中若是ip地址和端口为127.0.0.1:3306默认值,则可以省略不写。格式入下:

Connection connection = DriverManager.getConnection("jdbc:mys ql:///jdbc", "XXX", "XXX");

还有就是参数省略之后的格式(如下)。

(String url, String user, String password);

(String url, Properties info(user,password));

(String url?user=账号&password=密码);

3.3、创建发送sql语句对象

  • 静态 statement

  • 预编译 preparedstatement

Statement statement = connection.createStatement();// 创建statement

statement可以发送mysql语句并且返回结果。但是存在一些问题,所以只能处理没有动态值的语句。

  • statement用法。

    • 1.创建statement

    • 2.拼接sql语句

    • 3.发送sql语句,返回执行结果。

  • statement缺点

    • 1.sql语句需要字符串拼接比较麻烦。

    • 2.只能拼接字符串类型,其它的数据库类型无法处理。

    • 3.可能发生注入攻击(动态值充当语句结构)。

  • preparedstatement用法 :

    • 1、编写sql语句结果,不包含动态值部分的语句,动态值的部分用’?‘代替。

    • 2、创建preparedstatement,并且传入动态值。

    • 3、动态值 占位符 赋值 ?单独赋值即可

    • 4、发送sql语句

String sql = "select * from t_user where account = ? and password = ?;"; PreparedStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setObject(1, account); preparedStatement.setObject(2, password);

其中第一行是编写sql语句结果,然后第二行是创建一个预编译的statement并且设置sql语句结果,第三四行是讲对应的动态值传入,第一个参数是对应的下标,第二个是要传入的动态值。

3.4、发送sql语句

String sql = "select * from t_user where account = '" + account + "' and password = '" + password + "';";

int i = statement.executeUpdate(sql);

ResultSet resultSet = statement.executeQuery(sql);

其中第一行就相当于是讲sql语句用字符串拼起来,然后同过statement发送到数据库。

ResultSet resultSet = preparedStatement.executeQuery();

preparedstatement跟statement用法一样只不过不需要再传参数了。

通过返回值的不同有两种不同的写法,executeQuery可以用来接收查询语句返回的结果,ResultSet是结果封装对象。

3.5、结果集解析

只有查询语句的返回结果需要解析,其它语句就比较简单了,就是一个值。

  • 单行 if(resultSet.next()) { }

  • 多行 while(resultSet.next()) { }

while(resultSet.next()) { int id = resultSet.getInt(10); String account1 = resultSet.getString(2); String password1 = resultSet.getString(3); String nickname = resultSet.getString(4); System.out.println(id + "--" + account1 + "--" + password1 + "--" + nickname); }

其中resultSet.next()每次的返回值就是是否读到数据,若是没有读到数据则返回的是0,反之为1。

查询语句的返回结果也是一个有行有列的数据,resultSet.next(),相当于是每次读入该对象容器中的一行数据,然后resultSet.getInt()、resultSet.getString()就是获取读到的数据。括号里面的参数可以直接按下标,就是从1开始,或者可以直接可以填字段名。

但是这种方法就比较麻烦,而且格式固定了,不能适用于其它格式的数据,所以在这里引用了一个其它方法。

ResultSetMetaData metaData = resultSet.getMetaData();

通过这个metaData可以获取列的数量,以及列名,这样就可以用一个循环来读取一整行的数据了。

int columnCount = metaData.getColumnCount(); // 获取列的数量

String columnLabel = metaData.getColumnLabel( i ); // 可以获取第 i 列的名字,有别名取别名,没别名取列名。

String columnLabel = metaData.getColumnName( i ); // 只能获取名字,不能获取别名。

// 下面 就是一个完整的读取resultSet容器的方法。

List list = new ArrayList<>(); ​ ResultSetMetaData metaData = resultSet.getMetaData();

int columnCount = metaData.getColumnCount();

while(resultSet.next()) ​ { ​ Map map = new HashMap();

for(int i = 1; i <= columnCount; i ++) ​ { ​ Object value = resultSet.getObject(i); ​ String a = metaData.getColumnLabel(i); ​ map.put(a, value); ​ } ​ list.add(map); ​ } ​ System.out.println("list" + list);

3.6、资源关闭

resultSet.close(); // 若不是查询语句就用不上该容器,就不用关闭该资源。 statement.close(); connection.close();

就是讲之前用的资源都关闭。

4、Jdbc扩展提升

4.1、获取主键值

在插入数据的时候,获取数据库自增长的主键。

其实就是在创建statement对象的时候传第二个参数

PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

意思就是在获取结果的时候顺便把主键值也传回来。通过下面的语句可以把传回的主键信息获取,解析跟之前是一样的方法,resultSet.next()移动光标之后resultSet.getObject()进行获取,若是知道对应值的类型可以直接使用对应类型的函数进行读取。

ResultSet resultSet = preparedStatement.getGeneratedKeys();

Object value = resultSet.getObject();

4.2、批量化插入操作

如果每次都按之前那样一个个插入并且返回结果,时间是很慢的。可以写一个循环插入一万条数据试一下。currentTimeMillis*();语句返回的是时间,两次时间差就是所用的时间。

PreparedStatement preparedStatement = connection.prepareStatement(sql); long start = System.currentTimeMillis();

for (int i = 0; i < 10000; i++) {

preparedStatement.setObject(1, "XXX"); ​ preparedStatement.setObject(2, "XXX"); ​ preparedStatement.setObject(3, "XXX"); ​ preparedStatement.executeUpdate(); } long end = System.currentTimeMillis();

System.out.println("执行一万次数据插入的时间为:" + (end - start));

仔细一想就可以知道,每次查数据之前都会在上一条数据插入成功并且返回结果之后进行,其实这部分完全没有必要,所以可以直接等所有的数据插入完之后再返回数据。但是下边的这个操作需要再建立连接的时候传一个参数,就是允许进行批量化操作。

Connection connection = DriverManager.getConnection("jdbc:mysql:///XXX?rewriteBatchedStatements=true", "root", "XXXX");

在编写sql语句的时候最后不能加分号,他会把所有的插入操作变成一条语句,因为插入语句后面一直加下去是可以插多条数据的。最后就是将addBach()语句,就相当于是把当前插入语句跟之前的操作语句连起来,等到最后executeBatche()一起发送过去并且传回答案。

PreparedStatement preparedStatement = connection.prepareStatement(sql); long start = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) {

preparedStatement.setObject(1, "XXX"); preparedStatement.setObject(2, "XXX"); preparedStatement.setObject(3, "XXX"); preparedStatement.addBatch(); } preparedStatement.executeBatch(); long end = System.currentTimeMillis(); System.out*.println("执行一万次数据插入的时间为:" + (end - start));

4.3、事务处理

事务添加是在业务方法中,利用try catch代码块,开启事务和提交事务和事务回滚。将connertion传入dao层即可,doa只负责使用,不要close();

该事务必须是同一个连接,所以在业务方法中建立连接,然后将该connectino传到dao层。然后在执行操作之前先将事务提交关闭,就是setAutoCommit(false),然后在进行操作,等到所有操作统一执行完之后并且没有出现问题,那么再一起提交,就是commmit()语句。等到出现问题的时候进行回滚rollback();

例如:

BankDao bankDao = new BankDao(); Class.forName("com.mysql.cj.jdbc.Driver"); Connection connection = DriverManager.getConnection("jdbc:mysql:///jdbc?user=root&password=zy040928"); try{ connection.setAutoCommit(false); bankDao.add(addAccount, money, connection); bankDao.sub(subAccount, money, connection); connection.commit(); }catch (Exception a){ connection.rollback(); throw a; }finally { connection.close(); -*}

5、Druid 连接池技术的使用

5.1、连接性能消耗问题

每一次使用都要建立连接,用完之后就销毁,但是在平常,使用的就只有一瞬间,建立连接和关闭连接的时间大于使用时间,这个时候,就会有点不合理了。

5.2、数据库连接池作用

连接池技术就是提前将连接建立好,然后用的时候就从池子里拿,用完之后再回收。这样就可以省去建立和销毁的时间。

若是池子中的连接用完了,那么连接池会申请新的连接放到池子中。

当池子中的连接达到最大总数,就不能再申请连接,只能等其它连接用完再用。

5.3、连接池的建立

5.3.1、硬连接池建立方式

1、首先是建立一个druid连接池对象

DruidDataSource druidDataSource = new DruidDataSource();

2、设置参数

前四个是必须要设置的参数,分别是url、用户名、密码、和注册驱动

初始化连接数量以及最大的连接数量是非必须的,有默认值。

druidDataSource.setUrl("url"); // url druidDataSource.setUsername("root"); // 用户名 druidDataSource.setPassword("root"); // 密码 druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); // 注册驱动 druidDataSource.setInitialSize(5); // 初始化连接数量 druidDataSource.setMaxActive(10); // 最大的量

3、获取连接

获取跟之前的操作是很像的,只不过是从druidDataSource中获取

Connection connection = druidDataSource.getConnection();

4、数据库crud操作

就是你要拿这个连接要干些什么。

5、回收连接

这里的connection是一个包装类,colse在这里是回收连接,而不是之前的关闭连接

connection.close();

5.3.2、软连接池建立方式

1、设置外部配置文件

注意:文件必须是 .properties文件,里面填写内容为:注册驱动、用户名、密码、url,四个必填项,以及其它的非必须项。

driverClassName=com.mysql.cj.jdbc.Driver username=root password=XXX url=jdbc:mysql:///XX initialSize=5

2、读取外部配置文件

src下的文件可以使用类加载器提供的方法,就是第二行代码,那个参数是该文件的地址。然后把输入流给properties即可。

Properties properties = new Properties(); InputStream ips = DruidUsePart.class.getResourceAsStream("druid.properties"); properties.load(ips);

3、创建连接池,获取连接

创建连接池就是通过读取的配置文件当参数创建连接池。获取连接的方式和硬连接是一样的。

DataSource dateSource = DruidDataSourceFactory.createDataSource(properties); Connection connection = dateSource.getConnection();

4、数据库crud

//正常使用

5、回收连接

跟硬连接方式是一样的。

connection.close()

5.4、工具类的封装与使用

5.4.1初步封装

封装注册驱动、建立连接、关闭连接。

每次写代码都创建一个会很麻烦,所以可以直接封装成一个工具类,使用的时候调用就行,还有一个问题就是在同一个操作中不同文件使用同一个connection的话需要向下进行传参,为了避免每次传参,创建一个线程本地变量,第一次创建的connection放到线程本地变量里面,之后的直接使用线程本地变量中的连接,而不用每次都创建一个新的。

下面就是一个相对完整的工具类,里面设置了全局变量线程本地变量用来存储创建过的connection,不同线程的线程本地变量是不同的,然后就是static里面写好初始化连接池对象的操作。需要注意的是在调用getClassLoader().getResourceAsStream("XXX");的时候,里面的参数填的是你的配置文件与src的相对路径。static没有地方可以抛异常,所以直接try一下。

不一样的是再释放连接的时候,需要清空线程本地变量数据然后事务状态回顾,最后再回收连接。

public class JdbcUtils { private static DataSource dataSource = null; private static ThreadLocal tl = new ThreadLocal<>(); static { // 初始化连接池对象 Properties properties = new Properties(); InputStream ips = JdbcUtils.class.getClassLoader().getResourceAsStream("druid.properties"); try { properties.load(ips); } catch (Exception e) { throw new RuntimeException(e); } try { dataSource = DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { try { throw new Exception(e); } catch (Exception ex) { throw new RuntimeException(ex); } } } public static Connection getConnection() throws SQLException { Connection connection = tl.get();// 获取线程本地变量 if(connection == null) //第一次没有 { //线程本地变量没有,连接池获取 connection = dataSource.getConnection(); tl.set(connection);//放入线程本地变量 } return connection ; } public static void freeConnection() throws SQLException { Connection connection = tl.get(); if(connection != null) { tl.remove(); //清空本地变量数据 connection.setAutoCommit(true);//事务状态回顾 false connection.close(); // 回收到连接池 } } }

5.4.2、非DQL语句封装

在传参数的时候只需要传一下sql语句以及动态值即可,object表示可变参数,数量不定。然后在函数内部获取连接,创建statement对象,占位符赋值,因为不确定个数所以直接用for循环,适用于所有情况。

需要注意的是不是所有情况都需要回收连接,比如事务代码中在结束的时候会关闭,那么在这里就需要判断一下是否是事务,因为在事务开始的时候会把提交关闭,所有只需要查看一下状态,若提交开启则不是事务直接回收连接即可。最后返回结果。

public abstract class BaseDao { public int executeUpdate(String sql, Object... params) throws SQLException { Connection connection = JdbcUtils.getConnection(); PreparedStatement preparedStatement = connection.prepareStatement(sql); for(int i = 1; i <= params.length; i ++) { preparedStatement.setObject(i, params[i - 1]); } int rows = preparedStatement.executeUpdate(); preparedStatement.close(); if(connection.getAutoCommit()) // 若是当前是业务处理,那么肯定关闭了提交,此时就不能释放连接。 { JdbcUtils.freeConnection(); } return rows; }

}

5.4.3、DQL语句封装

DQL语句相比于非DQL语句会有些难度,因为非DQL语句的返回值只是int,而DQL语句不一样。

T表示声明一个泛型不确定类型,关于占位符的赋值跟之前是一样的。

list创建也是有一个泛型,等到在里面的时候函数获取完值和属性名,只需要先将属性名规定一下然后将值放进去即可,弄完之后加入链表。

public List executeQuery(Class clazz, String sql, Object... params) throws Exception{ Connection connection = JdbcUtils.getConnection(); PreparedStatement preparedStatement = connection.prepareStatement(sql); if(params == null && params.length != 0){ for(int i = 1; i <= params.length; i ++){ preparedStatement.setObject(i, params[i - 1]); } } ResultSet resultSet = preparedStatement.executeQuery(); ResultSetMetaData metaData = resultSet.getMetaData(); int columnCount = metaData.getColumnCount(); List list = new ArrayList<>(); while(resultSet.next()) { T t = clazz.newInstance(); // 调用类的无参构造函数实例化对象 for(int i = 1; i <= columnCount; i ++) { Object value = resultSet.getObject(i); // 获取指定下标的值 String a = metaData.getColumnLabel(i); // 获取当前列的属性名 Field field = clazz.getDeclaredField(a); field.setAccessible(true); field.set(t, value); } list.add(t); } resultSet.close(); preparedStatement.close(); if(connection.getAutoCommit()) { JdbcUtils.freeConnection(); } return list; }

5.4.4、封装后的使用

在其它地方想要使用这两个工具类JdbcUtils 、BaseDao的步骤是:

1、首先将驱动拉过来,druid 和 mysql-connector的jar包,然后使用。

2、将配置文件设置好,最好放在src文件下,否则的话使用该工具类需要修改5.4.1代码里面的一个参数,最后需要见配置文件中的参数改成你自己的相关信息。

3、将两个工具类复制到项目中,大概为api下的utils中

4、根据项目要求建立一个新的dao软件包,里面放实现操作的一些文件,看看项目中需要什么方法,在dao层去创建即可。

5、最后是实现dao层的方法,在dao层里面去继承一下BaseDao,因为里面是之前实现的一些基础操作。

6、结束、检测。

你可能感兴趣的:(java)