一、JDBC简介

1、JDBC是什么

JDBC: Java DataBase Connectivity (java语言连接数据库)

2、本质

sun公司指定的一套接口(interface)
java.sql.*包下

3、思考:为什么SUN制定一套JDBC接口呢?

  • 因为每一个数据库的底层实现原理都不一样。
  • Oracle数据库有自己的原理。
  • MySQL数据库也有自己的原理。
  • MS SqlServer数据库也有自己的原理。
  • ….
  • 每一个数据库产品都有自己独特的实现原理。

JDBC 中的sql语句不需要写分号

二、JDBC 编程6步

第一步:注册驱动

(作用:告诉Java程序,即将要连接的是哪个品牌的数据库)

不常用:

  1. DriverManager.registerDriver(new com.mysql.jdbc.Driver());
  2. Driver d = new com.mysql.jdbc.Driver();
  3. DriverManager.registerDriver(d);
  4. Class.forName("com.mysql.jdbc.Driver")

常用:

  1. Class.forName("com.mysql.jdbc.Driver");
  2. Driver 里面的静态代码块可以注册驱动,forName() 类加载可以执行注册驱动
  3. 参数是字符串,字符串可以写到配置文件里面

第二步:获取连接

(表示JVM的进程和数据库进程之间的通道打开了,这属于进程之间的通信,重量级的,使用完之后一定要关闭通道。)

url:统一资源定位符(网络中某个资源的绝对路径)

  • 协议
  • IP
  • 端口
  • 资源名

http://182.200.7:80/index.html

  • http:// 通信协议
  • 182.61.200.7 服务器ip地址
  • 80 服务器上软件的端口
  • index.html 是服务器上某个资源名

:::info JDBC 的协议:**String url = "jdbc:mysql://localhost:3306/库名";**
用户名:String name = "root";
密码:String password = "0000";
**Connection conn = DriverManager.getConnection(url,name,password);** :::

第三步:获取数据库操作对象

(专门执行sql语句的对象)(不常用,请使用预编译的数据库操作对象)

**Statement stmt = conn.createStatement();**

获取预编译的数据库操作对象
PrepareStatement ps = ``**conn.PrepareStatement(**``**sql**``**)**

第四步:执行SQL语句(DQL DML….)

专门执行DML语句(insert delete update) 返回int

  1. Stirng s = insert into 表名 values(插入的数据);
  2. int count = stmt.executeUpdate(s); // 这个返回值是指被影响的条数

专门执行DQL的语句(select)返回ResultSet

  1. String a = "select * from 表名"
  2. ResuletSet rs = stmt.executeQuery(a); 返回一个ResultSet对象

第五步:处理查询结果集

(只有当第四步执行的是select语句的时候,才有这第五步处理查询结果集。)

第六步:释放资源

(使用完资源之后一定要关闭资源。Java和数据库属于进程间的通信,开启之后一定要关闭。)

  • 在finally语句块中(保证资源一定释放)
  • 遵循从小到大依次关闭
    • 也就是说先关闭数据库操作对象,再关闭连接
    • 分别对其try catch

三、SQL注入

当 输入账号密码是以下这种格式的时候,密码不对也可以进入(黑客技术)
账号:fdsa
密码:fdsa’ or ‘1’=’1

1、原因

用户输入的信息中含有sql语句的关键字,这些关键字被编译进去,导致原来的sql语句原意被扭曲,进而达到sql注入

2、解决sql注入问题

  • 只要用户输入的信息,不参与sql语句的编译过程,就解决了
  • 要想用户信息不参与sql语句的编译,那么必须使用java.sql.preparedStatement 接口
  • 他继承了Statement接口,Statement能执行的,他也能执行
    • preparedStatement是预编译的数据库操作对象
  • preparedStatement原理

    • 预先对sql语句的框架进行编译,然后再给SQL语句传“值”
      获取预编译的数据库操作对象
      String sql = "select * from t_user where logname = ? and longpassword = ?"
      这里面的? 是占位符,一个?将来接收一个“值”
      占位符不能用单引号括起来
      获取预编译的数据库操作对象
      PrepareStatement ps = conn.PrepareStatement(sql)
      给占位符传值后执行
      ps.setString(``占位符的下标,值``);
      jdbc``中的下标都是从``1``开始的
      执行sql ResultSet1 = ps.executeQuery() ; 这里面就不用传sql语句进去了

      3、对比Statement 和 PreparedStatement

      3.1 Statement

  • 存在sql注入问题

  • 执行效率低
  • 编译一次执行一次

    3.2 preparedStatement

  • 解决了sql注入问题

  • 执行效率高
  • 编译一次执行n次
  • 99% 以上使用

    3.3 什么时候使用Statement

  • 当这个项目需要进行sql拼接的话,必须使用Statement

  • 例如升序降序
  • 如果是要传值就用PreparedStatement

增删改用PreparedStatement占位符的方式

四、处理查询结果集

1、遍历结果集

  • 需要循环结果集(resultSet)才能取出来元素

**rs.next()**
返回一个boolean类型的数值 ,返回true表示有数据,返回false表示没有数据

  • rs.getString(下标) 第几列
  • ★rs.getString(“列名”) 用列名程序更加健壮
    • 如果起了别名,必须是放别名
    • 特点:不管数据库中是什么数据类型,都已String的形式取出来
    • JDBC中所有下标从1开始!
  • 循环结果集

    1. while(rs.next()){
    2. String name = rs.getString("name");
    3. System.out.println(name);
    4. }
  • 除了以String的类型取出来,还可以以特定的类型取出

    • int s = rs.getInt("``编号``");
    • double s1 = rs.getDouble("``奖金``")``;
  • 也可以用**rs.getString(1)** 里面传一个数字表示取第几列,但是不常用【表示取第一列】

五、JDBC事务

JDBC中的事务是自动提交的,只要执行一条DML语句,则自动提交一次(默认)
但是在实际的业务中,通常都是N条DML共同联合才能完成,必须保证这些DML语句同时成功或失败

禁用事务的自动提交 (在获取到Connection 之后 设置自动提交为false)

**conn.setAutoCommit(false);**

提交事务

**conn.commit();**

回滚
  1. catch里面写
  2. if(conn != null){
  3. conn.rollback();
  4. }
  5. catch (ClassNotFoundException | SQLException e) {
  6. e.printStackTrace();
  7. try {
  8. if (conn == null) {
  9. conn.rollback();
  10. }
  11. } catch (SQLException ex) {
  12. ex.printStackTrace();
  13. }
  14. }

Conn.setAutoCommit(true)设置自动提交开启

在使用数据库连接池的时候,一个Connection会被使用多次,所以建议 在事务结束后调用该方法
获取/设置当前连接的隔离级别
**获取:Conn.getTransactionIsolation();**
返回一个数字
**设置:conn.setTransactionIsolation(Connection.常量);**
避免脏读就行 Connection.TRANSACTION_READ_COMMITED 【2级别】

六、数据库连接池

1、为什么需要数据库连接池

因为在创建Connection对象的时候需要0.05s-1s的时间,需要数据库连接的时候就创建出来一个,用完就关闭。这样如果同时几百人或近千人同时在线,频繁的连接数据库,这样服务器会崩溃

  • 数据库的连接资源没有得到很好的重复利用
  • 对于每一次数据库连接,使用完后都得断开
  • 这种开发不能控制创建的连接对象数两

    2、数据库连接池技术

    思想:为数据库连接建立一个”缓冲池”,预先在缓冲池中放入一定数量得连接,在需要Connection对象时,直接从中取出使用即可,然后用完放回去
    数据库连接池负责分配,管理和释放数据库连接,它可以重复得使用一个Connection,而不是创建一个新的

    3、多种开源得数据库连接池

  • C3P0数据库连接池 (稳定,速度慢)

  • DBCP数据库连接池 (速度快,不稳定)
  • Druid(德鲁伊)数据库连接池 (主流,阿里巴巴的)

JDBC的数据库连接池顶级接口** **``**javax.sql.DataSource**

4、C3P0实现数据库连接池方式

  1. 使用前先导入 mysqljarC3P0jar
  2. //获取C3P0的数据库的连接池
  3. ComboPooledDataSource cpds = new ComboPooledDataSource();
  4. // 注册驱动
  5. cpds.setDriverClass("com.mysql.jdbc.Driver");
  6. //url 用户名 密码
  7. cpds.setJdbcUrl("jdbc:mysql://localhost:3306/test");
  8. cpds.setUser("root");
  9. cpds.setPassword("0000");
  10. //设置初始化数据库连接池中的链接数(表示有池子里10个Connection)
  11. cpds.setInitialPoolSize(10);
  12. // 拿一个Connection (连接)
  13. conn = cpds.getConnection();
  14. // 表示不用了之后 把连接池关闭 DataSources中的静态方法 (一般情况不会关闭连接池)
  15. DataSources.destroy(cpds);
  16. 上面的这种,用户名密码 都写到了程序里面属于硬编码,可以通过配置文件的方式解耦合
  17. 配置文件的名字必须为 c3p0-config.xml
  18. 因为不常用,所以就用记了,主要看德鲁伊连接池

5、DBCP数据库连接池方式

  1. (使用配置文件):
  2. driverClassName=com.mysql.jdbc.Driver
  3. url=jdbc:mysql://localhost:3306/test
  4. username=root
  5. password=0000
  6. initialSize=10
  7. //测试DBCP数据库连接池
  8. //创建流
  9. Properties pros = new Properties();
  10. FileInputStream is = new FileInputStream(new File("E:\\java\\Tast001\\Data\\src\\main\\resources\\is.properties"));
  11. //加载配置文件
  12. pros.load(is);
  13. //
  14. DataSource dataSource = BasicDataSourceFactory.createDataSource(pros);
  15. Connection connection = dataSource.getConnection();

6、Druid数据库连接池

使用前先导入 mysql的jar和druid的jar
用完了之后也要在finally里面 进行 close() 关闭
不管是什么数据库连接池技术,他都是DataSource的接口实现类
配置信息
未命名图片.png

  1. 配置文件 druid.propertis
  2. url=jdbc:mysql://localhost:3306/test
  3. username=root
  4. password=0000
  5. driverClassName=com.mysql.jdbc.Driver
  6. //初始化时建立的连接个数
  7. initialSize=10
  8. //最大数量
  9. maxActive=1000
  1. 代码实现工具类
  2. //静态私有的DataSource
  3. private static DataSource dataSource;
  4. static{
  5. Properties pros = new Properties();
  6. InputStream is = DruidUtils.class.getResourceAsStream("/DruidUtils.properties");
  7. try{
  8. System.out.println(is);
  9. pros.load(is);
  10. dataSource = DruidDataSourceFactory.createDataSource(pros);
  11. } catch (Exception e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. public static Connection getConnection() throws Exception {
  16. return dataSource.getConnection();
  17. }

未命名图片.png
未命名图片.png

七、Apache提供的DBUtils

1、依赖

  1. <dependency>
  2. <groupId>commons-dbutils</groupId>
  3. <artifactId>commons-dbutils</artifactId>
  4. <version>1.6</version>
  5. </dependency>

2、使用QueryRunning类进行增删改查

2.1 增:

  1. //调用自己编写的 Druid数据库连接池方法拿到 Connection
  2. Connection conn = DTest.getConnection();
  3. //使用阿帕奇的QueryRunning类进行增删改查
  4. QueryRunner queryRunner = new QueryRunner();
  5. /*第一个参数 一个Connection
  6. * 第二个参数 sql语句
  7. * 第三个参数 sql里面的? 具体的值 (因为是可变长参数,所以随便传值)
  8. * */
  9. 此方法已被重载多次,传值有很多方式
  10. String sql = "insert into t_money values(?,?,?)";
  11. int aa = queryRunner.update(conn, sql, null, "aa", 1000);
  12. 增删改都可以用这个方法 queryRunner.update

2.2 查询:

查询一条记录

  1. BeanHandler:是ResultSetHandler接口的实现类,用于封装表中的一条记录,返回一个具体的对象需提供 对应表的实体类,重写toString()方法
  2. Connection conn = DTest.getConnection();
  3. QueryRunner runner = new QueryRunner();
  4. String sql = "select id, name, money from t_money where id = ?";
  5. /*
  6. * 第一个参数:传一个连接 Connection
  7. * 第二个参数:sql语句
  8. * 第三个参数:ResultSetHandler(这是个接口) 结果集处理器
  9. * 第四个参数:可变长度参数 ? 里面的值
  10. * */
  11. /*第三个参数需要一个ResultSetHandler接口实现类,而BeanHandler也是其中一个表示
  12. 返回一个对象,创建一个BeanHandler实例,创建时传进去对应实体类的class*/
  13. BeanHandler<t_money> bean = new BeanHandler<>(t_money.class);
  14. t_money t = runner.query(conn, sql,bean ,3);
  15. System.out.println(t);

查询多条记录

  1. 使用BeanListHandler接口实现类
  2. // 查询多条记录
  3. Connection conn = DTest.getConnection();
  4. QueryRunner runner = new QueryRunner();
  5. String sql2 = "select id from t_money where name = ?";
  6. BeanListHandler<t_money> bean2 = new BeanListHandler<>(t_money.class);
  7. List<t_money> query = runner.query(conn, sql2, bean2, "aa");
  8. //得到List集合后 迭代集合
  9. ListIterator<t_money> tI = query.listIterator();
  10. while (tI.hasNext()){
  11. System.out.println(tI.next());
  12. }

查询特殊值

  1. 使用ScalarHandler接口实现类
  2. // 特殊查询
  3. Connection conn = DTest.getConnection();
  4. QueryRunner runner = new QueryRunner();
  5. String sql3 = "select count(*) from t_money";
  6. //ScalarHandler
  7. ScalarHandler scalarHandler = new ScalarHandler();
  8. Object query1 = runner.query(conn, sql3, scalarHandler);
  9. System.out.println(query1);

自定义一个ResultSetHandler接口实现类

  1. String sql4 = "select id,name from t_money where id = ?";
  2. ResultSetHandler<t_money> handler = new ResultSetHandler<t_money>(){
  3. @Override
  4. public t_money handle(ResultSet resultSet) throws SQLException {
  5. if (resultSet.next()){
  6. int id = resultSet.getInt("id");
  7. String name = resultSet.getString("name");
  8. return new t_money(id,name);
  9. }
  10. return null;
  11. }
  12. };
  13. t_money query = runner.query(conn, sql4, handler,3);
  14. System.out.println(query);