JDBC_2

112 阅读10分钟

抽取JDBC工具类:JDBCUtils

  • 目的:简化书写
  • 分析:
    • 注册驱动也抽取
    • 抽取一个方法获取连接对象
      • 需求:不想传递参数(麻烦),还得保证工具类的通用性。
      • 解决:配置文件
        • jdbc.properties
          • url=
          • user=
          • password=
    • 抽取一个方法释放资源
  1. 新建一个文件

properties.png

联想截图_20221223164054.png

package jdbc.util;

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

/*
* JDBC工具类
* */
public class JDBCUtils {

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

    /*
     * 文件的读取,只需要读取一次即可拿到这些值,使用静态代码块
     * */
    static {
//        读取资源文件,获取值
//        1. properties集合类
        Properties pro=new Properties();
//        2.加载文件
        try {
            pro.load(new FileReader("src/jdbc.properties"));
//            3.获取数据,赋值
            url= pro.getProperty("url");
            user= pro.getProperty("user");
            password= pro.getProperty("password");
            driver=pro.getProperty("driver");
//            4.注册驱动
            try {
                Class.forName(driver);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    /*
     * 获取连接
     * 返回的是连接对象
     * */
    public static Connection getConnection(String url,String user,String password) throws SQLException{
        return DriverManager.getConnection("jdbc:mysql:///study","root","123456");
    }
    
    public static Connection getConnection() throws SQLException{
        return DriverManager.getConnection(url,user,password);
    }

    /*
     * 释放资源
     * stmt
     * conn
     */
    public static void close(Statement stmt, Connection conn) {
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }

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


动态获取src目录下的文件(dreuid.properties)路径

pro.load(new FileReader("src/jdbc.properties"));

替换成

//            获取src路径下的文件的方式--->  ClassLoader 类加载器
            ClassLoader classLoader=JDBCUtils.class.getClassLoader();
           URL res= classLoader.getResource("druid.properties");
           String path= res.getPath();
            System.out.println(path);
//        2.加载文件
//            pro.load(new FileReader("D:/project/JDBC02/src/druid.properties"));
            pro.load(new FileReader(path));

如果路径中有中文会报错,只要改成全英文即可



练习(利用工具类)

  • 需求

    • 通过键盘录入用户名和密码。
    • 判断用户是否登录成功,如果成功,则提示登录成功,失败,则提示登录失败。
  • 步骤

    • 创建一个数据表(user)

user表.png

package jdbc;

import java.sql.*;
import java.util.Scanner;

public class JDBCPractice6 {
    public static void main(String[] args) {
//   1. 键盘录入,接受用户名和密码
    Scanner sc=new Scanner(System.in);
    System.out.println("请输入用户名");
    String username=sc.nextLine();
    System.out.println("请输入用密码");
    String password=sc.nextLine();
//    2. 调用方法
    boolean flag=new JDBCPractice6().login(username,password);

//    3.判断结果,输出不同语句
    if(flag){
//        登录成功
        System.out.println("登录成功");
    }else{
        System.out.println("用户名或密码错误");
    }
}
/*
* 登录方法
* */
    public boolean login(String username,String password){
        if(username==null||password==null){
            return false;
        }
        Connection conn=null;
        Statement stmt=null;
        ResultSet rs=null;
//        连接数据库判断是否连接成功

//        1.获取连接
        try {
//             conn= JDBCUtils.getConnection();
            try {
                Class.forName("com.mysql.jdbc.Driver");
                try {
//                     2.获取连接
                    conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/study", "root", "123456");
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }

//            2.定义SQL
            String sql="select * from user where username='"+username+"' and password = '"+password+"'";
//            3.获取执行SQL的对象
            stmt =conn.createStatement();
//            4.执行查询
            rs=stmt.executeQuery(sql);
//            5.判断
                return rs.next();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JDBCPractice6.close(rs,stmt,conn);
        }
        return false;
    }

    public static void close(ResultSet executeQuery, Statement preparedStatement,  Connection connection) {
        if(executeQuery!=null){
            try {
                executeQuery.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if(preparedStatement!=null){
            try {
                preparedStatement.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }

        }
        if(connection!=null){
            try {
                connection.close();     //工具类
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }

    }

}


PreparedStatement(SQL注入)

执行SQL的对象

  1. SQL注入问题 :在拼接SQL时,有一些SQL的特殊关键字参与字符串的拼接。会造成安全性问题 。
    1. 输入用户随便,输入密码:a' or 'a' ='a
    2. sql: select * from user where username = 'wkks' and password = 'a' or 'a'='a'

SQL注入.png

  1. 解决SQL注入问题:使用preparedStatement对象来解决
  2. 预编译SQL:参数使用?作为占位符
  3. 步骤:
    1. 导入jar包mysql-connertor-java-5.1.37-bin.jar
    2. 注册驱动
    3. 获取数据库连接对象connection
    4. 定义sql
      • 注意:sql的参数使用?作为占位符。如:select * from user where username=? and password=?;
    5. 获取执行sql语句的对象prepareStatement        Connection.PrepareStatement(String sql)
    6. 给?赋值
      • 方法:setxxx(参数1,参数2)
        • 参数1:?的位置编号从1开始
        • 参数2:?的值
    7. 执行sql,接收返回结果
    8. 处理结果
    9. 释放资源
  4. 注意:后期都会使用preparedStatement来完成增删改查的所有操作
    1. 可以防止SQL注入
    2. 效率更高
package jdbc;
import java.sql.*;
import java.util.Scanner;

public class PrepareStatement8 {
    public static void main(String[] args) {
//   1. 键盘录入,接受用户名和密码
        Scanner sc=new Scanner(System.in);
        System.out.println("请输入用户名");
        String username=sc.nextLine();
        System.out.println("请输入用密码");
        String password=sc.nextLine();
//    2. 调用方法
        boolean flag=new PrepareStatement8().login(username,password);

//    3.判断结果,输出不同语句
        if(flag){
//        登录成功
            System.out.println("登录成功");
        }else{
            System.out.println("用户名或密码错误");
        }
    }

    /*
     * 登录方法
     * */
    public boolean login(String username,String password){
        if(username==null||password==null){
            return false;
        }
        Connection conn=null;
        PreparedStatement pstmt=null;
        ResultSet rs=null;
//        连接数据库判断是否连接成功

//        1.获取连接
        try {
//             conn= JDBCPractice6.getConnection();
            try {
                Class.forName("com.mysql.jdbc.Driver");
                try {
//                     2.获取连接
                    conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/study", "root", "123456");
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }

//            2.定义SQL
            String sql="select * from user where username=? and password = ?";
//            3.获取执行SQL的对象
            pstmt =conn.prepareStatement(sql);
//            给?赋值
            pstmt.setString(1,username);
            pstmt.setString(2,password);
//            4.执行查询,不需要传递参数
            rs=pstmt.executeQuery();
//            5.判断
            return rs.next();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            PrepareStatement8.close(rs,pstmt,conn);
        }
        return false;
    }

    public static void close(ResultSet executeQuery, PreparedStatement preparedStatement,  Connection connection) {
        if(executeQuery!=null){
            try {
                executeQuery.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if(preparedStatement!=null){
            try {
                preparedStatement.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }

        }
        if(connection!=null){
            try {
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }

    }

}

JDBC控制事务

  1. 事务:一个包含多个步骤的操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。

  2. 操作

    1. 开启事务
    2. 提交事务
    3. 回滚事务
  3. 使用Connection对象来管理事务

    • 开启事务:setAutoCommit(boolean autoCommit):调用该方法设置参数为false,即开启事务
      • 在执行SQL之前开启事务
    • 提交事务:commit()
      • 当所有SQL都执行完提交事务
    • 回滚事务:rollback()
      • 在catch中回滚事务
package jdbc;

import jdbc.util.JDBCUtils;

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

/*
* 事务操作
* */
public class affair9 {

    public static void main(String[] args) {
        Connection conn=null;
        PreparedStatement pstmt1=null;
        PreparedStatement pstmt2=null;
//        1.获取连接
        try {
            try {
                Class.forName("com.mysql.jdbc.Driver");
                conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/study", "root", "123456");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
//            开启事务
            conn.setAutoCommit(false);
//            2.定义SQL
//            2.1 张三-500
            String sql1="update account set balance =balance-? where id=?";
//            2.2 李四+500
            String sql2 ="update account set balance =balance+? where id=?";
//            3.获取执行SQL对象
            pstmt1=conn.prepareStatement(sql1);
            pstmt2=conn.prepareStatement(sql2);
//            4. 设置参数
            pstmt1.setDouble(1,500);
            pstmt1.setInt(2,1);
            pstmt2.setDouble(1,500);
            pstmt2.setInt(2,2);
//            5.执行SQL
            pstmt1.executeUpdate();
//            手动制造异常
//            int i=3/0;
            pstmt2.executeUpdate();
//            提交事务
            conn.commit();

        } catch (Exception e) {
//            事务回滚
            try {
                if(conn!=null) {
                    conn.rollback();
                }
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        }finally {
            JDBCUtils.close(pstmt1,conn);
            JDBCUtils.close(pstmt2,null);
        }
    }


}

数据库连接池

  1. 概念:其实是一种容器(集合),存放数据库连接的容器
    • 当系统初始化好后,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器。
  2. 好处
    1. 节约资源
    2. 用户访问高效
  3. 实现:
    1. 标准接口:DateSource   javax.sql包下的
      1. 方法:
        • 获取连接:getConnection()
        • 归还连接·:如果连接对象Connection是从连接池中获取的,那么调用Connection.close()方法,则不会再关闭连接了。而是归还连接
    2. 一般我们不去实现它,有数据库厂商实现
      1. C3P0:数据库连接池技术
      2. Druid:数据库连接池实现技术,由阿里巴巴提供的

C3P0:数据库连接技术

* 步骤:
    1. 导入jar包 两个)   c3p0-0.9.5.2.jar   mchange-commons-java-0.2.12.jar
    2. 定义配置文件:
        * 名称:  c3p0.properties or c3p0-config.xml
        * 路径:  直接将文件放在src目录下即可
    3. 创建核心对象  数据库连接池对象CombopooledDataSource
    4. 获取连接:getConnection

Druid

数据库连接池实现技术,由阿里巴巴提供的

  1. 步骤
    1. 导入jar包 druid-1.0.9.jar
    2. 定义配置文件:
      • 是prooerties形式的
      • 可以叫任意名称,可以放在任意位置
        driver=com.mysql.jdbc.Driver
        url=jdbc:mysql://localhost:3306/study?useSSL=false
        username=root
        password=123456
        #初始化连接数量
        initialSize=5
        #最大连接数
        maxActive=10
        #最大等待时间
        maxWait=3000            
        

Snipaste_2023-01-12_19-34-44.png 3. 加载配置文件 4. 获取数据库连接池对象:通过工厂来获取 DruidDataSourceFactory 5. 获取连接:getConnection

package jdbc;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;

public class DruidDemo {
    public static void main(String[] args) {
        try {
//        1.导入jar包
//        2.定义配置文件
//        3.加载配置文件
        Properties pro=new Properties();
        InputStream is=DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
//            System.out.println(is);
        pro.load(is);
//        4. 获取连接池对象
            DataSource ds=DruidDataSourceFactory.createDataSource(pro);
            //        5. 获取连接
            Connection conn =ds.getConnection();
            System.out.println(conn);
        } catch (Exception e) {
            e.printStackTrace();
        }


    }
}
  1. 定义工具类
    1. 定义一个类JDBCUtils
    2. 提供静态代码块加载配置文件,初始化连接池对象
    3. 提供方法
      1. 获取连接方法:通过数据库连接池获取连接
      2. 释放资源
      3. 获取连接池的对象
package jdbc.util;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
/*
Druid连接池的工具类
* */
public class JDBCUtils {
//    1. 定义成员变量
   private static DataSource ds;
   static{
       try {
//        1. 加载配置文件
           Properties pro =new Properties();
           pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
//            2.获取DataSource
           ds= DruidDataSourceFactory.createDataSource(pro);
       } catch (IOException e) {
           e.printStackTrace();
       } catch (Exception e) {
           e.printStackTrace();
       }
   }
   /*
   * 获取连接的方法
   * */
   public static Connection getConnection() throws SQLException{
       return ds.getConnection();
   }

   /*
   * 获取连接池方法
   * */
   public static DataSource getDataSource(){
           return ds;
   }
}
package jdbc;

/*
* 使用新的工具类
* */

import jdbc.util.GjlClose;
import jdbc.util.JDBCUtils;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.util.Scanner;

public class DruidDemo2 {
    public static void main(String[] args) {

        PreparedStatement pstmt=null;
        Connection conn=null;
        Scanner sc=new Scanner(System.in);
        System.out.println("请输入用户名");
        String name=sc.nextLine();
        System.out.println("请输入余额");
        Double money=sc.nextDouble();
        /*
        * 完成添加操作,给account表添加一条记录
        * */
        try {
//            1.获取连接
//            Class.forName("com.mysql.jdbc.Driver");
//            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/study", "root", "123456");
            conn= JDBCUtils.getConnection();

//            2.定义·SQL
            String sql="insert into account values(null,?,?)";
//            3获取pstmt对象
            pstmt=conn.prepareStatement(sql);
//            4.给?赋值
            pstmt.setString(1,name);
            pstmt.setDouble(2,money);
//            5.执行SQL
            int count=pstmt.executeUpdate();
            System.out.println(count);
//            pstmt.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
//            6.释放资源
            GjlClose.close(pstmt,conn);
        }
    }
}

Spring JDBC:JDBC Template

  1. Spring框架对JDBC的简单封装。提供了一个JDBCTemplate对象简化JDBC的开发
  2. 步骤:
    1. 导入jar包
      Snipaste_2023-01-09_21-06-11.png
    2. 创建JDBCTemplate对象。依赖于数据源DataSource
      • JDBCTemplate template=new JDBCTemplate(ds);
    3. 调用JDBCTemplate的方法来完成CRUD的操作
      • update():执行DML语句。增、删、改语句
      • queryForMap():查询结果将结果集封装为map集合,将列名作为key,将值作为value,将这条记录封装为一个map集合
        • 注意:这个方法查询的结果集长度只能是1
      • queryForList():查询结果将结果集封装为list集合
        • 注意:将每一条记录封装为一个Map集合,再将Map集合装载到List集合中
      • query():查询结果,将结果封装为JavaBean对象
        • query的参数:RowMapper
          • 一般我们使用BeanPropertyRowMapper实现类。可以完成数据到到JavaBean的自动封装
          • new BeanPropertyRowMapper<类型>(类型.class)
      • queryForObject:查询结果,将结果封装为对象
        • 一般用于聚合函数的查询
package jdbc.Template;

import jdbc.util.JDBCUtils;
import org.springframework.jdbc.core.JdbcTemplate;

/*
* JdbcTemplate入门
* */
public class JDBCTemplate {
    public static void main(String[] args) {
//        1.导入jar包
//        2.创建JDBCTemplate对象
        JdbcTemplate template=new JdbcTemplate(JDBCUtils.getDataSource());
//        3.调用方法
        String sql="update account set balance=5000 where id=?";
        int count=template.update(sql,3);
        System.out.println(count);
    }
}

4. 练习

  • 需求:
    • 修改1号数据salary为10000
    • 添加一条记录
    • 删除刚才添加的记录
    • 查询所有的记录,将其封装为Map集合
    • 查询所有记录,将其封装为Emp对象的List集合
    • 查询总记录数
package jdbc.Template;

import jdbc.cn.Emp;
import jdbc.util.JDBCUtils;
import org.junit.Test;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

public class JDBCTemplateDemo1 {
    //Junit单元测试,可以让方法独立执行

    //       1.获取JDBCTemplate对象
    private JdbcTemplate template=new JdbcTemplate(JDBCUtils.getDataSource());
    @Test
    public void test1(){
//        2.定义sql
        String sql="update emp set salary=1000 where id=1";
//        3.执行sql
        int count =template.update(sql);
        System.out.println(count);

    }
    /*
    * 添加一条记录
    * */
    @Test
    public void test2(){
//        2.定义sql
        String sql="insert into emp (id,name,age) values(?,?,?)";
        int count=template.update(sql,null,"萧策",25);
        System.out.println("添加了"+count+"条记录");
    }
   /*
   * 删除刚才添加的记录
   * */
    @Test
    public void test3(){
        String sql="delete from emp where id=?";
        int count =template.update(sql,21);
        System.out.println("删除了"+count+"条记录");
    }
    /*
    * 查询id为1的记录,将其封装为Map集合
    * */
//    注意这个方法查询的结果集长度只能是1
    @Test
    public void test4(){
        String sql = "select * from emp where id=?";
        Map<String,Object> map=template.queryForMap(sql,1);
        System.out.println(map);
    }
    @Test
    public void test5(){
        String sql ="select * from emp";
        List<Map<String,Object>> list=template.queryForList(sql);
//        for(Map<String,Object>stringObjectMap:list){
//            System.out.println(stringObjectMap);
//        }
        System.out.println(list);
    }
    @Test
    public void test6_1(){
        String sql="select * from emp";
        List<Emp> list=template.query(sql, new RowMapper<Emp>() {
            @Override
            public Emp mapRow(ResultSet rs, int i) throws SQLException {
                Emp emp=new Emp();
                int id=rs.getInt("id");
                String name = rs.getString("name");
                int age = rs.getInt("age");
                String job = rs.getString("job");
                double salary = rs.getDouble("salary");
                Date joindate = rs.getDate("entrydate");
                emp.setId(id);
                emp.setAge(age);
                emp.setName(name);
                emp.setJob(job);
                emp.setJoindate(joindate);
                emp.setSalary(salary);
                return emp;
            }
        });
        for (Emp emp:list){
            System.out.println(emp);
        }
    }
    @Test
    public void test6_2(){
        String sql="select * from emp";
        List <Emp> list=template.query(sql,new BeanPropertyRowMapper<Emp>(Emp.class));
        for(Emp emp:list){
            System.out.println(emp);
        }
    }
    @Test
    public void test7(){
        String sql="select count(id) from emp";
        Long total=template.queryForObject(sql,Long.class);
        System.out.println("总共有"+total+"条记录");
    }

}