JDBC的基本使用

JDBC:Java Database Connectivity,java数据库连接,其实就是用java语言来操作数据库。

JDBC的本质:其实就是官方(sun公司)定义的一套用来操作所有关系型数据库的规则,即接口。然后各个厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)来编程,而真正执行的是驱动jar包中的实现类,即多态的方式。

JDBC的使用步骤

  1. 导入驱动jar包:mysql-connector-java-5.1.37-bin.jar
  2. 注册驱动
  3. 获取数据库连接对象Connection
  4. 定义sql
  5. 获取执行sql语句的对象Statement
  6. 执行sql语句,接收返回结果
  7. 处理结果
  8. 释放资源

DriverManager类

注册驱动

java.sql.DriverManager类,管理JDBC的驱动。 通常用来【注册驱动】和【获取数据库连接】。

注册驱动的方法,这是一个静态方法:

通常,我们并不需要使用这个方法来注册驱动,而是通过下方的代码,加载一个类文件:

加载的文件是导入的jar包中定义的一个类,该类中定义了一个静态代码块:

 

也就是说,jar包中已经有了相关的文件帮我们进行了注册,我们只需加载一下这个类文件就可以了。

 

获取数据库连接

获取数据库连接的方法getConnection,也是一个静态方法,参数传递指定连接的路径(ip+端口+指定数据库),用户名及密码。返回的是一个Connection对象。

注意:这里传递的连接路径,不同的数据库写法有所差异,mysql的写法为:

 

如果连接的是本机的mysql服务器,并且mysql服务器默认服务端口是3306,那么可以将IP地址和端口号省略不写。写成jdbc:mysql:///数据库名称

Connection接口

java.sql.Connection接口,用来建立与特定数据库的连接。我的理解:连接数据库和执行sql语句之间的一个桥梁。

获取执行sql语句的对象:

  • createStatement方法

  • prepareStatement方法

管理事务的方法

  • 开启事务的方法,setAutoCommit,参数传递false即为开启事务

  • 提交事务,commit

  • 回滚事务,rollback

Statement接口

java.sql.Statement接口,用于执行静态sql语句。

执行sql语句的方法

  • execute方法,可以执行任意的sql语句,此方法并不常用,了解即可。

  • executeUpdate,多用于执行数据的增删改语句,返回一个int值,该值为sql语句影响的行数。我们可以通过这个行数来判断sql语句是否执行成功,如果行数>0,表示执行成功,否则失败。如果传递DDL语句,则会返回一个0。

  • executeQuery方法,通常会传递sql查询语句,返回的是个结果集对象。

ResultSet接口

java.sql.ResultSet接口,封装查询结果。

next方法,光标向下移动一行(光标初始位置在表头行,注意查询返回的是一个满足条件的新表格)。如果该方法返回false,则说明已经不指向数据行,也就是此时位于数据行之后了。可以用于判断是否还有数据。

getXxx(参数)方法,用来获取特定类型特定位置的数据,后面的Xxx就是你想获取的数据类型,并且返回获取的数据,比如:int getInt()、String getString()等。此方法的参数有两种传递方式:

  1. 传递int值,表示想要获取第几列的数据,比如你想获取的某String类型值在第3列,则方法为:getString(3),注意,列数从1开始。
  2. 传递String值,直接传递要获取的数据所在列的字段名,比如getInt("age")。

结果集的遍历(此处以遍历各个员工姓名、性别和工资为例):

代码实现(行数不确定,用while循环):

注意:结果集只能一行一行地循环,不能一格一格地循环。而且要知道有哪些字段,以及这些字段分别是什么类型。还可以指定获取哪些字段数据。

表和实体类的映射

对比一下表和类就可以发现,可以将整个表映射成一个类,表名对应类名,表字段对应类的成员变量。

将上面的学生表映射成一个类:

然后可以定义一个方法,遍历数据库表为这些成员变量赋值:这样就可以将每行数据都封装成一个对象。再将这些对象存到集合中:

public static List<Student> findAll(){
    //定义一个List集合,用于存放Student对象
    List<Student> list = new ArrayList<>();

    //为了释放资源,需要提升几个对象的作用域
    Connection conn = null;
    Statement stmt = null;
    ResultSet rs =null;
    //开始遍历数据库,将字段值赋给对应的成员变量
    try {
        //注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //获取数据库连接对象
        conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db1","root","ROOT");
        //获取执行sql语句的对象
        stmt = conn.createStatement();
        //定义sql语句
        String sql = "select * from student";
        //开始执行sql语句,返回一个结果集
        rs = stmt.executeQuery(sql);
        //声明一个学生对象,因为局部变量必须要赋值,所以赋初始值为null
        Student s = null;
        //遍历结果集
        while(rs.next()){
            //开始依次获取每一行数据
            int id = rs.getInt("id");
            String name = rs.getString("name");
            int age = rs.getInt("age");
            String sex = rs.getString("sex");
            String address = rs.getString("address");
            int math = rs.getInt("math");
            int english = rs.getInt("english");

            //用带参构造函数创建对象,传递获取的值
            s = new Student(id,name,age,sex,address,math,english);
            //将对象存进list集合
            list.add(s);
        }

    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (SQLException e) {
        e.printStackTrace();
    }finally {
        //如果对象创建成功,则需要释放资源
        if(rs != null){
            try {
                rs.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();
            }
        }

    }

    return list;
}

然后调用该方法,返回的就是一个存放每行数据的对象集合,再想使用这些数据,就很方便了。

JDBC工具类

上面写的代码重复度很高,我们可以抽取出一个工具类,来简化代码。java并不直接提供此工具类,需要自己根据实际情况来编写。

工具类中的方法一般定义成静态的,方便通过类名直接调用。

通常需要抽取这几个部分,省得每次都写重复代码:

  • 注册驱动的部分
  • 获取连接对象的部分
  • 释放资源的部分

先定义比较长但是比较简单的资源释放部分,要注意的是,当用executeUpdate执行语句时不会产生结果集对象,而用executeQuery执行时会多一个结果集对象,所以释放资源的方法需要定义两种重载形式:

//当用executeUpdate执行sql时,只需要释放两个对象资源
public static void close(Statement stmt, Connection conn){

    if(stmt != null){
        try {
            stmt.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    if(conn != null){
        try {
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

//当用executeQuery执行sql时,需要释放三个对象资源
public static void close(ResultSet rs,Statement stmt, Connection conn){

    if(rs != null){
        try {
            rs.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();
        }
    }
}

获取连接对象部分的抽取,同样需要定义一个方法,里面写获取连接对象的语句,并返回一个Connection对象。这个方法中需要数据库路径、账号和密码这三个参数。

按理说,肯定不能将这三个值写死,那么就需要给这个方法传参,那调用这个方法时根本就没有得到简化(毕竟原来也只有一条语句),既不能写死,又不能传参,怎么办呢?用读取配置文件的方式来得到这三个值:

因为配置文件只用加载一次,所以将加载配置文件的方法定义在静态代码块中,让他随着类的加载而加载:

注意

  • 因为异常抛出要依赖于方法,所以静态代码块中的异常无法抛出,只能try…catch来处理
  • 配置文件中要定义路径、用户以及密码的信息,

另外,我们可以将驱动注册也写在配置文件中,然后在静态代码块进行注册。

综合代码如下:

import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.sql.*;
import java.util.Properties;

public class JDBCUtils {

    private static String url;
    private static String user;
    private static String password;
    private static String driver;

    static{
        //获取当前类的类加载器
        ClassLoader classLoader = JDBCUtils.class.getClassLoader();
        //通过加载器找到目标文件
        URL resource = classLoader.getResource("jdbc.Properties");
        //将url对象转化成路径
        String path = resource.getPath();
        System.out.println(path);

        //通过Properties集合来加载文件
        Properties ps = new Properties();
        try {
            ps.load(new FileReader(path));
        } catch (IOException e) {
            e.printStackTrace();
        }

        url = ps.getProperty("url");
        user = ps.getProperty("user");
        password = ps.getProperty("password");
        //同时进行驱动的注册
        driver = ps.getProperty("driver");
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }


    //获取连接对象的方法
    public static Connection getConnection(){
        Connection conn = null;
        try {
            conn = DriverManager.getConnection(url,user,password);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

    //当用executeUpdate执行sql时,只需要释放两个对象资源
    public static void close(Statement stmt, Connection conn){

        if(stmt != null){
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if(conn != null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    //当用executeQuery执行sql时,需要释放三个对象资源
    public static void close(ResultSet rs,Statement stmt, Connection conn){

        if(rs != null){
            try {
                rs.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();
            }
        }
    }
}

PreparedStatement接口

先看一个登陆案例

public class LoginTest {

    public static void main(String[] args) {
        //输入用户名和密码
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String user = sc.next();
        System.out.println("请输入密码:");
        String password = sc.next();

        //用户登录,并提示登录结果
        boolean flag = login(user, password);
        if(flag){
            System.out.println("登录成功!");
        }else{
            System.out.println("用户名或密码错误!");
        }
    }

    public static boolean login(String user,String password){
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        boolean result = false;
        try {
            //注册驱动,并获取数据库连接
            conn = JDBCUtils.getConnection();
            //创建sql执行对象
            stmt = conn.createStatement();
            //定义sql语句
            String sql = "select * from login where user = '"+user+"' and password = '"+password+"'";
            //执行sql语句
            rs = stmt.executeQuery(sql);
            //返回查询结果,如果存在该用户名和密码,那么就是对哒
            result = rs.next();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally{
            JDBCUtils.close(rs,stmt,conn);
        }
        return result;
    }
}

以上代码存在一定的问题,当用户名随便输入,密码输入a’ or 'a' = 'a时,会导致sql语句为以下格式,导致能够登陆成功。

像这样的问题被称为【sql注入问题】,即在拼接sql语句时,有一些sql特殊的关键字参与了字符的拼接,会造成安全性问题。

要想解决sql注入问题就要使用java.sql.PreparedStatement接口,他是Statement接口的一个子接口,表示预编译的 SQL 语句的对象。

预编译的SQL:定义的sql语句中,参数使用?作为占位符

如果使用PreparedStatement接口来处理sql语句,那么使用数据库的步骤就有一些变动:

  • 1、通过Connection接口中的prepareStatement方法来获取一个PreparedStatement对象,并且该方法需要传入预编译的sql语句,所以sql语句要在这之前就定义好。

  • 2、获取PreparedStatement对象之后,需要用该类中的setXxx方法来为?赋值,Xxx为相应的数据类型,并且该方法有两个参数:
    • 第一个参数表示第几个问号,编号从1开始;
    • 第二个参数表示要赋的值
  • 3、开始执行sql语句,PreparedStatement接口中同样有executeQuery和executeUpdate两个方法,因为已经传递过sql语句了,所以这两个方法没有参数。

使用示例:

注意:后期都会使用PreparedStatement接口来完成增删改查的所有操作,可以有效防止sql注入,提高安全性。

JDBC事务管理

主要使用Connection中的三个方法:开启事务setAutoCommit,提交事务commit,回滚rollback

有几个问题需要注意:

  • 在事务开始之前开启事务
  • 在所有sql执行完提交事务
  • 在多条sql之间出现异常则要回滚,注意几个问题:
  • 回滚语句要放在出现异常后跳转到的地方,即catch里面
  • 最好给这个回滚语句加个判断条件,如果Connection不为空,再进行回滚,否则会出现空指针异常。
  • 因为无论出现什么异常都需要进行回滚,所以catch最好捕获大的异常Exception。

示例如下: 

public static void main(String[] args) {
    Connection conn = null;
    PreparedStatement pstmt1 = null;
    PreparedStatement pstmt2 = null;
    
    //创建sql执行对象
    try {
        //注册驱动,并获取数据库连接
        conn = JDBCUtils.getConnection();
        //开启事务
        conn.setAutoCommit(false);
        //定义转账的sql语句
        String sqlone = "update account set balance = balance - ? where id = ?";
        String sqltwo = "update account set balance = balance + ? where id = ?";
        //获取sql执行对象
        pstmt1 = conn.prepareStatement(sqlone);
        pstmt2 = conn.prepareStatement(sqltwo);

        //给sql语句中的?赋值
        pstmt1.setDouble(1,500);
        pstmt1.setInt(2,3);
        pstmt2.setDouble(1,500);
        pstmt2.setInt(2,4);

        //执行sql语句
        pstmt1.executeUpdate();
        pstmt1.executeUpdate();
        //提交事务
        conn.commit();
    } catch (Exception e) {
        //回滚事务
        if(conn != null){
            try {
                conn.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        }
        e.printStackTrace();
    }
}

数据库连接池

为了解决使用数据库时频繁地连接数据库——释放连接带来的系统损耗,实现数据库连接的重复利用,就引入了数据库连接池。

数据库连接池就是一个存放数据库连接的容器,当系统初始化后,容器会被创建好,容器中会申请一些连接对象,当用户访问数据库时,从容器中获取连接对象,用完之后再归还给容器。

这样就大大节约了资源,提高了访问数据库的效率。

如何实现:

java.sql.DataSource接口下,有一个getConnection方法用来获取数据库连接

如果连接是从连接池里获取的,那么调用Connection中的close方法,则不会再关闭连接了,而是归还连接。

一般我们不去实现该接口,由数据库厂商来实现

1、C3P0:一种比较古老的数据库连接池实现技术

2、Druid:由阿里巴巴提供的一种数据库连接池实现技术。

C3P0连接池

使用步骤:

1、导入连接池jar包(两个):c3p0-0.9.5.2.jar和mchange-commons-java-0.2.12.jar,另外别忘了要导入数据库的驱动jar包。

2、定义配置文件

  • 名称:c3p0.properties或者c3p0-config.xml,必须是这两个名字,否则找不到。
  • 路径:放在工程的src包下即可。

3、(代码开始)创建核心对象,使用DataSource的实现类ComboPooledDataSource

4、获取连接:getConnection

关于c3p0的配置文件

在new ComboPooledDataSource时如果是空参就调用默认配置,其实在这个配置文件里还可以定义更多的配置信息:

在new ComboPooledDataSource时传入指定配置的名称就能使用了。通常使用默认的即可。

Druid连接池

使用步骤:

1、导入jar包,druid-1.0.9.jar,另外别忘了导入数据库驱动jar包

2、定义配置文件:文件为properties类型的,可以取任意名称,放在任意目录下,所以需要自己手动加载

3、利用Properties集合去手动加载配置文件

4、获取连接池对象:通过工厂来获取 DruidDataSourceFactory.createDataSource(),该方法有一个参数,需要传入一个Properties对象。

5、获取连接对象

使用示例:

Druid工具类

定义一个工具类,里面提供如下功能:

提供静态代码块加载配置文件,初始化连接池对象

提供方法:

  • 获取连接的方法:通过数据库连接池获取连接
  • 释放资源的方法
  • 获取连接池的方法

代码如下:

注:由于截图原因,释放资源和获取连接池的getDs方法略。

JdbcTemplate

Spring JDBC,Spring框架对JDBC的简单封装,提供了一个JDBCTemplate对象简化JDBC的开发。

使用步骤:

  • 1、导入jar包
  • 2、创建JdbcTemplate对象,依赖于连接池对象DataSource
  • 3、调用JdbcTemplate中的方法来完成数据库的CRUD操作:

update方法

update是执行增、删、改语句的方法,参数可以传递一个增删改的sql语句,如果有预编译的sql语句,则按顺序依次传入每个问号的值(不用写编号),比如:

query型方法

queryForMap()方法,查询结果并将结果集封装成Map集合,注意,此方法查询的结果集只能是一行数据,然后将字段名和对应值映射成键值对,封装到Map集合中。

queryForList()方法,查询结果并将结果集封装成List集合,该方法将每行数据封装成Map集合,然后再将多个Map集合存入List集合:

query()方法,查询结果并将结果集封装成javabean对象。之前将数据表映射成类时,进行了一系列的获取值和赋值的过程:

query方法可以将这一过程进行简化,这个方法可以传递两个参数:第一个参数传递sql查询语句,第二个参数可以将查询出的结果通过RomMapper接口封装成一个LIst集合,这个接口我么可以自己实现(一大堆获取值和赋值的过程),也可以用已经有的实现类BeanPropertyRowMapper来将结果集封装成List集合,泛型使用映射的类,参数传递映射类的字节码文件:

注意:如果在映射类中将数据类型定义成基本类型,一旦数据库中出现null,将会出现无法转换的异常,null值只能用引用类型来接收,所以最好将映射类的数据类型定义成包装类。

queryForObject方法,可以将查询到的一行数据封装成一个对象。

如果是静态sql,需要传入两个参数,分别是sql语句和new BeanPropertyRowMapper<映射类>(映射类.class)。

如果是动态sql,在后面紧接着传递实际参数即可。

该方法还可以用来执行聚合函数,参数传递sql语句,以及查询的聚合函数类型的.class文件。返回一个包装类对象。还可以紧接着传递参数。

注意,这些方法中的参数都是可变参数,可以传递数组。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值