打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Java开发之JDBC学习分享

数据库的操作步骤:

1:导入数据库的驱动jar包----> 导入实现类

2:加载驱动 -----> 告诉他我要连接哪个数据库

3:获得连接------>让我和数据库发生关系

4:获得预处理语句对象->让我 发送sql指令到数据库

5:执行SQL命令;--->预处理语句对象.execute("SQL 命令行");

6:释放资源 -------> 切断我和数据库的联系

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import org.junit.Test;


public class JDBCTest {
    
  @Test
  public void testJDBC() throws Exception {
    // 1.加载驱动
    //DriverManager.registerDriver(new com.mysql.jdbc.Driver());
            Class.forName("com.mysql.jdbc.Driver");//直接使用此方式:因为底层已经实现上面的步骤
    // 2:获得连接----->让java程序和数据库建立联系 . 让程序和数据库发生关系
    Connection connection = DriverManager.getConnection("jdbc:mysql://IP地址:端口号(默认3306)/数据库的名字", "root", "root");
    // 3:获得预处理语句对象-->让java发送SQL语句到数据库
    Statement statement = connection.createStatement();
    // 4.执行需要执行的SQL语句
    statement.execute("create table girl(id int , name varchar(20),age int);");
    // 4:释放资源
    statement.close();
    connection.close();
  }
}

如何找到你要操作的数据集库:

写死 jdbc:不同的数据库的厂商名://ip地址:端口号/数据库的库名

操作mysql方式1:jdbc:mysql://localhost:3306/数据库的库名

操作mysql方式2:jdbc:mysql://127.0.0.1:3306/数据库的库名

如果 操作的数据库在本机 ,并且数据库的端口号是 3306

可以简写 方式:jdbc:mysql:///数据库的库名

抽取工具类:DAO规范的设计

规范DAO组件的类名以及包名. 域java培训名倒写.模块名.组件名----> cn.wolfcode.smis

1.定义domain包以及类名(操作DAO都是去操作一张表,表应该对应着一个javabean, 取一个特殊的名字domain)

cn.wolfcode.smis.domain

Student.java

2.定义DAO包以及DAO接口.

cn.wolfcode.smis.dao

IxxxDAO---> XXX表示domain.

IStudentDAO.java

3.定义DAO实现类的包以及实现类

cn.wolfcode.smis.dao.impl

StudentDAOImpl.java

4.测试类

cn.wolfcode.smis.test

StudentDAOImplTest.java

5.工具类

cn.wolfcode.smis.util

JDBCUtil.java

规范的开发步骤---完成保存操作:

1.创建数据库表.

2.根据数据库表创建对应的domain包和类.

注意:表的列的名字和类型与类的属性名和类型要一一匹配.

3.通过domain来创建DAO包以及接口.

4.根据接口生成实现类.

5.根据接口/实现类生成测试类.完成测试的代码.

6.实现一个方法,测试一个方法,测试通过,再写写一个方法.

预编译语句对象-PreparedStatement:

预编译语句对象-PreparedStatement:

PreparedStatement是静态语句对象的子类.所以,父类中的所有的方法都可以调用到.但是一般我们不会调用.调用自己特有的方法.

预编译语句对象是通过带有占位符的SQL模板来创建对象的.

SQL模板:带有占位符?的SQL.相同的操作,SQL语句其实是一样的,仅仅是参数不一样而已.

参数确定不了,使用占位符?来表示.

常见API:

1. 获取预编译语句对象.

使用数据库连接对象:

PreparedStatement prepareStatement(String sql); 传递的是SQL模板,有占位符

2.在执行SQL之前,一定要给占位符设置值.

void setObject(int parameterIndex,Object x):

parameterIndex: 参数索引,在JDBC中,索引是从1开始的.

3.执行SQL命令:

注意,不要调用父类带有参数的方法.

DQL操作:增删改

在JDK中,把如何解析结果集的操作,封装到了一个对象中.ResultSet.

需求:

1.查询id为10的学生信息

2.查询所有的学生信息.

ResultSet对象:

把如何解析结果集的过程封装成了对象.

boolean next(): 往下移动一行,如果返回true,表示有数据,否则没有数据.要获取数据,一定要调用next方法.

Object getObject(int columnIndex): 根据列的位置来取数据.索引从1开始.

Object getObject(String columnLabel): 根据列名来获取数据

优化:

1.连接数据库四要素重复写很多遍(抽取到公共的位置,只写一遍).

2.Class.forName加载字节码,字节码只会加载一次(使用静态代码块去加载注册驱动).

3.硬编码.(连接数据库四要素).

4.代码重复(DML操作, DQL操作).

5.每次获取数据库连接对象,用完直接就关闭了.浪费.

代码:

配置文件:

符号javaBean的Student类:

DAO接口的定义:

接口的测试类:

JDBC的工具类:

package cn.wolfcode.smis.util;


import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;


//工具类
public class MyJDBCUtil {
  // 私有化构造器
  private MyJDBCUtil() {
  }


  static Properties p = new Properties();
  // 静态代码块
  static {
    try {
      // 加载配置文件
      p.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("pd.properties"));
      // 加载驱动器
      Class.forName(p.getProperty("driverClassName"));
    } catch (Exception e) {
      e.printStackTrace();
    }
  }


  // 获得连接对象
  public static Connection getConnection() {
    Connection connection = null;
    try {
      connection = DriverManager.getConnection(p.getProperty("url"), p.getProperty("username"),
          p.getProperty("password"));
    } catch (Exception e) {
      e.printStackTrace();
    }
    return connection;
  }


  // 关闭资源
  public static void close(Connection connection, Statement statement) {
    try {
      if (statement != null) {
        statement.close();
      }
    } catch (SQLException e) {
      e.printStackTrace();
    }
    try {
      if (connection != null) {
        connection.close();
      }
    } catch (SQLException e) {
      e.printStackTrace();
    }


  }


  // 重载关闭资源
  public static void close(Connection connection, Statement statement, ResultSet resultSet) {
    try {
      if (statement != null) {
        statement.close();
      }
    } catch (SQLException e) {
      e.printStackTrace();
    }
    try {
      if (connection != null) {
        connection.close();
      }
    } catch (SQLException e) {
      e.printStackTrace();
    }
    try {
      if (resultSet != null) {
        resultSet.close();
      }
    } catch (SQLException e) {
      e.printStackTrace();
    }


  }


}

接口的实现类:

package cn.wolfcode.smis.dao.impl;


import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;


import cn.wolfcode.smis.dao.IStudentDAO;
import cn.wolfcode.smis.domain.Student;
import cn.wolfcode.smis.util.MyJDBCUtil;


public class StudentDAOImpl implements IStudentDAO {


  @Override
  public void insert(Student student) {
    // 获得连接对象
    Connection connection = MyJDBCUtil.getConnection();
    // 获得预编译对象
    PreparedStatement statement = null;
    try {
      statement = connection.prepareStatement("insert into student(id,name,age)  values(?,?,?)");
      // 设置站位符
      statement.setObject(1, student.getId());
      statement.setObject(2, student.getName());
      statement.setObject(3, student.getAge());
      // 执行
      statement.execute();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      // 关闭资源
      MyJDBCUtil.close(connection, statement);
    }
  }


  @Override
  public void delete(Long id) {
    // 获得连接对象
    Connection connection = MyJDBCUtil.getConnection();
    // 获得预编译对象
    PreparedStatement statement = null;
    try {
      statement = connection.prepareStatement("delete from student where id = ?");
      // 设置站位符
      statement.setObject(1, id);
      // 执行
      statement.execute();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      // 关闭资源
      MyJDBCUtil.close(connection, statement);
    }
  }


  @Override
  public void update(Student student) {
    // 获得连接对象
    Connection connection = MyJDBCUtil.getConnection();
    // 获得预编译对象
    PreparedStatement statement = null;
    try {
      statement = connection.prepareStatement("update student set name = ? ,age = ? where id = ?");
      // 设置站位符
      statement.setObject(1, student.getName());
      statement.setObject(2, student.getAge());
      statement.setObject(3, student.getId());
      // 执行更新方法
      statement.executeUpdate();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      // 关闭资源
      MyJDBCUtil.close(connection, statement);
    }
  }


  @Override
  public Student selectOne(Long id) {
    // 获得连接对象
    Connection connection = MyJDBCUtil.getConnection();
    // 获得预编译对象
    PreparedStatement statement = null;
    Student student = null;
    try {
      statement = connection.prepareStatement("select * from student where id = ?");
      // 设置站位符
      statement.setObject(1, id);
      // 执行查询,返回结果集
      ResultSet resultSet = statement.executeQuery();
      if (resultSet.next()) {// 结果集中有元素
        // 得到结果集中的元素---->结果集中是一个个字段对应的值,听过字段名获得值,然后创建个对象返回
        Object name = resultSet.getObject("name");
        Object age = resultSet.getObject("age");
        // 创建对象
        student = new Student(id, (String) name, (Integer) age);
      }
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      // 关闭资源
      MyJDBCUtil.close(connection, statement);
    }
    return student;
  }


  @Override
  public List<Student> selectAll() {
    // 创建List
    List<Student> list = new ArrayList<>();
    // 获得连接对象
    Connection connection = MyJDBCUtil.getConnection();
    // 获得预编译对象
    PreparedStatement statement = null;
    try {
      statement = connection.prepareStatement("select * from student");
      // 执行查询,返回结果集
      ResultSet resultSet = statement.executeQuery();
      // if (resultSet.next()) {// 结果集中有元素
      // 用while循环将结果集中所有的元素取出来
      while (resultSet.next()) {
        // 得到结果集中的元素---->结果集中是一个个字段对应的值,听过字段名获得值,然后创建个对象返回
        Object id = resultSet.getObject("id");
        Object name = resultSet.getObject("name");
        Object age = resultSet.getObject("age");
        // 创建对象并将对象添加进list中
        list.add(new Student((Long) id, (String) name, (Integer) age));
      }
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      // 关闭资源
      MyJDBCUtil.close(connection, statement);
    }
    return list;


  }


}

上述代码依然有重复:使用模板方法可再进行优化。

注入问题:经典案例登录安全问题;

@Test
  public void testLogin() throws Exception {
    // 模拟账户的登录,理解 statement和preparestatement的区别
    String username = "will";
    // String password = "123456";
    // 注入问题
    String password = "' or '1'='1";
    // loginByStatement(username, password);
    // loginByPrepareStatement(username, password);
    loginByPrepareStatement1(username, password);
  }


  // 没有使用预处理语句对象:不安全 String password = "' or '1'='1";时可以屏蔽SQL语句的语义。导致成功登录
  private void loginByStatement(String username, String password) throws Exception {
    // 验证 username 和 password 在数据库中的表 是否存在这一条数据
    Connection connection = JDBCUtil.getConnection();
    Statement statement = connection.createStatement();
    String sql = "select * from user where username = '" + username + "' and password = '" + password + "'";
    ResultSet resultSet = statement.executeQuery(sql);
    if (resultSet.next()) {
      System.out.println("登录成功");
    } else {
      System.out.println("账户或者密码错误,登录失败");
    }
    JDBCUtil.close(connection, statement, resultSet);
  }


  // 模拟登录案例--->使用预处理对象:安全
  private void loginByPrepareStatement1(String username, String password) throws Exception {
    // 把参数和数据库中的客户信息对比,相同才能登录成功
    // 1连接数据库
    Connection connection = JDBCUtil.getConnection();
    // 2获得预处理语句对象
    PreparedStatement statement = connection
        .prepareStatement("select * from user where username = ? and password = ?");
    // 2.1设置占位符
    statement.setObject(1, username);
    statement.setObject(2, password);
    // 3查询结果
    ResultSet resultSet = statement.executeQuery();
    // 判断数据库中是否有这条用户数据
    if (resultSet.next()) {
      System.out.println("登录成功!");
    } else {
      System.out.println("账户不存在!登录失败!!");
    }
    // 关闭资源
    JDBCUtil.close(connection, statement, resultSet);
  }

事务:经典案例,转账安全问题。

期望结果:如果转账成功,那么数据发生变化.如果转账失败,那么账户余额不应该改变,应该还是转账之前的状态.

上面这种操作,就是数据库中的事务操作.

事务(Transaction,简写为tx):

在数据库中,所谓事务是指一组逻辑操作单元,使数据从一种状态变换到另一种状态。

为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:

当每个逻辑操作单元全部完成时,数据的一致性可以保持,

而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。

事务的操作:

先定义开始一个事务,然后对数据作修改操作,这时如果提交(commit),这些修改就永久地保存下来,如果回退(rollback),数据库管理系统将放弃您所作的所有修改而回到开始事务时的状态。

事务:指构成单个逻辑工作单元的操作集合

事务处理:保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),要么整个事务回滚(rollback)到最初状态

说白了:

1.需要把多个操作放在一起作为一个整体.

2.全部成功,才叫成功,否则一个失败,全部都失败.

事务的ACID属性:

1. 原子性(Atomicity):原子在化学中,是最小单位,不可以再分割了.

原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

2. 一致性(Consistency):保证数据的完整性.

事务必须使数据库从一个一致性状态变换到另外一个一致性状态。(数据不被破坏)

3. 隔离性(Isolation):

事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

4. 持久性(Durability):

持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响

事务相关的细节:

1):默认情况下,事务在执行完DML操作就自动提交.

2):查询操作,其实是不需要事务的.但是,一般的,我们在开发中都把查询放入事务中.

3):开发中,代码完全正确,没有异常,但是就是数据库中数据不变.

意识:可能没有提交事务.

4):在MySQL中,只有InnoDB存储引擎支持事务,支持外键,MyISAM不支持事务.

5):以后事务我们不应该在DAO层处理,应该在service层控制(提出).

6):事务在讲解框架和项目的时候都会再讲.

事务操作,在一次连接中进行多次操作.需要将多个操作放在一个整体中,就需要使用事务.

操作模板:

try{
  // 手动开启一个事务
  conn.setAutoCommit(false);
  操作1...
  操作2...
  操作N...
  // 全部成功,提交事务
  conn.commit();
} catch(Exception e){
  // 异常代码处理
  // 如果失败,需要回滚
  conn.rollback();
} finally{
  // 关闭资源
}

注意:

只有在调用了commit方法之后,数据库才会真正的发生变化.全部成功,调用commit方法.如果失败了一定要调用rollback方法.

原因:在开启事务的时候,就有一个事务锁的存在.必须要调用commit或者rollback才可以释放.

  @Test
  public void testTransaction() {
    // 模拟转账的效果 西门吹雪 转账给 菠萝吹雪 1000钱。如果转账过程中出现异常,则金额不应该发生改变。// 1:先判断 西门吹雪 有没有钱
    Connection connection = JDBCUtil.getConnection();
    PreparedStatement statement = null;
    ResultSet resultSet = null;
    try {
      // 取消自动提交,让完整的事物执行完后再进行收动提交
      connection.setAutoCommit(false);
      statement = connection.prepareStatement("select * from account where name = '西门吹雪' and balance >= 1000");
      resultSet = statement.executeQuery();
      // 只有金额足够才能转账
      if (resultSet.next()) {
        System.out.println("开始转账");
        // 西门吹雪的 钱 减少1000
        statement.executeUpdate("update account set balance = balance - 1000 where name = '西门吹雪'");
        // int ret = 10 / 0;//制造异常---->如果转账过程中出现异常,则金额不应该发生改变。// 菠萝吹雪金莲的钱 增加 1000
        statement.executeUpdate("update account set balance = balance + 1000 where name = '菠萝吹雪'");


        // 手动提交事务
        connection.commit();
      } else {
        System.out.println("没钱!!!,我的二叔是公安局长");
      }
    } catch (Exception e) {
      System.out.println("转账异常中断!");
      e.printStackTrace();
      // 回退:出现异常时,将事务回退到最开始的状态。---->只有出现异常中断时才需要回退,所以放到catch语句中
      try {
        connection.rollback();
      } catch (SQLException e1) {
        e1.printStackTrace();
      }
    } finally {
      // 关闭资源:无论如何最后都要关闭资源
      JDBCUtil.close(connection, statement, resultSet);
    }
  }

获得自动生成的主键:获得注册后信息

1.设置一个标记,给数据库说,我要自动生成的主键.

在获取预编译语句对象的时候,传递要获取的标记.

Statement.RETURN_GENERATED_KEYS .

2.获取自动生成的主键.

// 模拟注册后显示id和注册时的信息
  @Test
  public void testInsert() throws Exception {
    // 注册时的输入的信息
    String username = "天河首富";
    String password = "lh0030";
    // 获得连接对象
    Connection connection = JDBCUtil.getConnection();
    String sql = "insert into user (username,password) values(?,?)";
    // 获得预处理语句对象
    PreparedStatement statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);// 获得自动生成的主键
    // 设置占位符的值
    statement.setObject(1, username);
    statement.setObject(2, password);
    // 执行更新操作
    statement.executeUpdate();
    // 把自动生成的主键放到了一个结果集对象中
    ResultSet keys = statement.getGeneratedKeys();
    // 如果插入成功就返回主键和插入的信息
    if (keys.next()) {
      // 获取自动生成的主键
      long id = (long) keys.getObject(1);
      System.out.println(id + "  " + username + "  " + password);
    }
    // 关闭资源
    JDBCUtil.close(connection, statement, keys);
  }

数据库连接池:

目前我们操作数据库的方式:

1.获取数据库连接对象.

2.执行SQL命令

3.解析结果集.

4.关闭资源(结果集对象,预编译语句对象,数据库连接对象)

执行完了一个SQL命令之后,就会关闭数据库连接对象.问题就出在这里.

为什么必须使用数据库连接池:

普通的JDBC数据库连接(Connection对象)使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间),数据库的连接是比较昂贵的(创建的成本比较大)。

需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。

数据库的连接资源并没有得到很好的重复利用.若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。

对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。

这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃.

说白了:

当前的操作数据库的方式是,花费了大量的资源和时间创建的非常昂贵的数据库连接对象,使用一次之后就关闭了.主要存在的问题是,没有充分使用到数据库连接对象.使用连接池来解决这个问题.

池化技术:管理资源对象,达到最高的效率.

池化技术能够减少资源对象的创建次数,提高程序的性能,特别是在高并发下这种提高更加明显。使用池化技术缓存的资源对象有如下共同特点:

1,对象创建时间长;

2,对象创建需要大量资源;3,对象创建后可被重复使用。比如thread,connection等对象都具有上面的几个共同特点。

在Java中,连接池使用javax.sql.DataSource接口来表示连接池.

DataSource(数据源)和连接池(Connection Pool)是同一个.

注意:DataSource仅仅只是一个接口,由各大服务器厂商来实现(Tomcat,JBoss).

常用的DataSource的实现:

DBCP: Spring框架推荐的

druid: 阿里巴巴的连接池(号称Java语言中性能最好的连接池)德鲁伊.

==============================================================

使用连接池和不使用连接池的区别在哪里?

如何获取Connection对象:

未使用连接池:

Connection conn = DriverManager.getConnection(url,username,password);

使用了连接池:

datasource对象.getConnection();// 返回的连接对象已经是一个功能增强的连接对象了.

只要获取了Connection对象,接下来的操作和以前是一模一样的.

关键在于:如何创建DataSource对象.

如何释放Connection对象(Connection对象.close()):

未使用连接池: 是和数据库服务器断开.

使用了连接池: 还给连接池.

使用数据库连接池创建JDBC的工具类:

方式1:

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;


//JDBC工具类
public class JDBCUtil {
  // 私有化构造器
  private JDBCUtil() {
  }


  static Properties p = new Properties();
  static {
    try {
      p.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("db.properties"));
      Class.forName(p.getProperty("driverClassName"));


    } catch (IOException e) {
      System.err.println("你的配置文件加载不到");
      e.printStackTrace();
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }


  }


  // 获得数据库的连接对象
  public static Connection getConnection() {
    // 获得 资源文件夹中的db.properties文件中数据库连接信息
    Connection connection = null;
    try {
      connection = DriverManager.getConnection(p.getProperty("url"), p.getProperty("username"),
          p.getProperty("password"));
    } catch (SQLException e) {
      e.printStackTrace();
    }
    return connection;
  }
 }

方式2:

import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.sql.DataSource;


import com.alibaba.druid.pool.DruidDataSourceFactory;//德鲁伊的jar包


//JDBC工具类
public class JDBCUtil {
  // 私有化构造器
  private JDBCUtil() {
  }


  static DataSource source = null;
  static {
    try {
      Properties p = new Properties();
      p.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("db.properties"));
      //这种方式创建对象
      source = DruidDataSourceFactory.createDataSource(p);
    } catch (IOException e) {
      e.printStackTrace();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }


  // 获得数据库的连接对象
  public static Connection getConnection() {
    Connection connection = null;
    try {
      connection = source.getConnection();
    } catch (SQLException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    return connection;
  }
 //省略关流操作,关流没变
 }
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
老调重弹:JDBC系列 之 <JDBC层次结构和基本构成>
JDBC常见面试题(修订版)
Java JDBC 理论笔记
JDBC详解学习文档
(2) Java SQL框架(java.sql.*)中常用接口详解
java.sql.SQLException: Can't call commit when autocommit=true(转) - 梦想飞扬的地方的日志 - 网易博客
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服