当前位置: 首页 > article >正文

JDBC相关

JDBC相关

ODBC

早期的数据库应用程序开发,因为没有通用的针对与数据库的编程接口,所以,开发人员需要学习相关数据库的API,才可以进行应用程序,这样增加了学习成本和开发周期。因此整个开发市场一直在呼吁有一套通用的编程接口

因为有市场需要,微软定义了一组用于数据库应用程序的编程接口ODBC(open database connectivity)。这一套方案大大缩短了程序的开发周期,可以让开发人员只需要调用同一套编程接口,无需考虑具体实现。

ODBC分为四个部分:

1. 应用程序:开发人员所写的代码,ODBC提供的调用接口
2. 驱动程序管理器:用于管理驱动程序的。
3. 驱动程序:对接口的实现部分,各个数据库厂商来完成的。
4. 数据源:就是连接数据库的一些参数:url,username,password

JDBC简介

Sun公司参考了ODBC方案,制定了一组专门为java语言连接数据库的通用接口JDBC(java database connectivity)。方便了java开发人员,开发人员不需要考虑特定的数据库的DBMS。JDBC不直接依赖于DBMS,而是通过驱动程序将sql语句转发给DBMS,由DBMS进行解析并执行,处理结果返回。 

简单点说,它为Java开发者提供了一种标准的方法来连接和操作各种关系型数据库。

注意:驱动程序:由数据库厂商自己实现,程序员只需要拿来使用即可。

JDBC工作原理 

第一步:注册驱动程序 
第二步: 请求连接 
第三步: 获取执行sql语句的对象,发送给DBMS 
第四步:返回结果集,程序员进行处理 
第五步: 关闭连接操作

 JDBC中常用API

JDBC API包含了一组类和接口,这些类和接口使得Java程序能够连接到数据库,执行SQL语句,并处理结果。

java.sql.DriverManager
java.sql.Connection
java.sql.Statement
java.sql.ResultSet

 DriverManager

JDBC的驱动管理类,负责加载和注册驱动,会根据所提供的连接信息(如URL、用户名和密码)自动选择合适的驱动程序。其提供了用于连接数据库的方法`getConnection(…)`

常用方法:

static Connection getConnection(String url, String user, String password)


- url:  连接指定数据库的地址            (比如,jdbc:mysql://ip:port/dbname) 
- user:  连接用户名 
- password:  密码

 Connection

是一个接口,代表了与数据库的一个会话;通过DriverManager的getConnection方法,程序可以建立与数据库的连接,返回该接口的一个实现类对象。可以用来获取Statement、PreparedStatement和CallableStatement等对象。

常用方法:

Statement createStatement();
作用:用于获取Statement对象

PreparedStatement  prepareStatement();
setAutoCommit(boolean flag)
commit();
rollback();
close();

Statement

Statement:也是一个接口,用于执行静态SQL语句。每次执行都会解析、编译和执行SQL语句,效率较低,但灵活性高。

常用方法:

execute(String sql):通常用于DDL 
executeUpdate(String sql):通常用于DML 
executeQuery(String sql):用于DQL

 PreparedStatement:用于执行预编译的SQL语句。预编译的SQL语句只需要解析、编译一次,之后可以多次执行,提高了执行效率。适用于需要多次执行相同或类似SQL语句的场景。

常用方法:

execute() ;------用于DDL和DML 
executeUpdate();-----用于DML 
executeQuery();-----用于DQL

CallableStatement:用于执行存储过程和函数。它可以接收参数、返回结果集和处理输出参数。

 ResultSet

是一个接口,表示从数据库执行DQL语句时返回的结果集。其内部维护了一个指针,该指针默认指向的是第一行之前的位置。next方法用于移动指针到下一行。 指针指向某一行时,就可以调用相关的方法获取这一行上的所有列数据。

常用方法:

next():光标方法,向下移动一行,

getDate(int columnIndex)
getDate(String columnLabel)

getString(int columnIndex) 
getString(String columnLabel)

getInt(int columnIndex) 
getInt(String columnLabel)

getDouble(int columnIndex) 
getDouble(String columnLabel)

原生JDBC入门编程

 在编写JDBC的原生代码时,先创建好项目,加载好相应的静态资源,如图片、第三方jar包等,

注意:jar包要add到Library里

然后编写步骤如下:

  1. 注册驱动

  2. 建立连接

  3. 获取执行对象

  4. 处理结果集

  5. 关闭连接

案例一:增加数据

向mysql的train2025这个库下的emp表中添加一条记录:
 10005, 'lucy', 'clerk','7369',now(),2000,300,40;

public class _01AddDemo {
    public static void main(String[] args) {
        Connection conn = null;
      try{
          //第一步:注册驱动
          // 低版本的驱动类:com.mysql.jdbc.Driver;
          Class.forName("com.mysql.cj.jdbc.Driver");
          //第二步:发送请求,获取连接对象
          // url :    jdbc:mysql://localhost:3306/train2025?serverTimezone=Asia/Shanghai&useTimezone=true  数据库后面要加问好和东八区时区
          // username: root 一般都是root
          // password: 自己数据库的密码
           conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/train2025?serverTimezone=Asia/Shanghai&useTimezone=true",
                  "root","123456");
          //第三步:获取语句对象,发送sql语句
          Statement stat = conn.createStatement();
          // 该方法会发送sql语句
          int i = stat.executeUpdate("insert into emp values(10005, 'lucy', 'clerk','7369',now(),2000,300,40)");
          //第四步:处理结果集
          System.out.println("受影响的条数:"+i);
          //第五步:关闭连接
      }catch (Exception e){
          e.printStackTrace();
      }finally {
          try {
              conn.close();
          } catch (SQLException e) {
              throw new RuntimeException(e);
          }
      }
    }
}

 案例二:更新数据

修改10005这个员工的姓名叫lily, 入职日期是2024-10-10,奖金500,部门编号是10号

public class _02UpdateDemo {
    public static void main(String[] args) {
        Connection conn = null;
        try{
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/train2025?serverTimezone=Asia/Shanghai&useTimezone=true",
                    "root","123456");
            Statement statement = conn.createStatement();
            int i = statement.executeUpdate("update emp set ename='lily',hiredate='2024-10-10',comm=500,deptno=10 where empno = 10005");
            System.out.println("受影响的条数:"+i);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                conn.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

 案例三:删除数据

删除部门号是null的所有员工信息

public class _03DeleteDemo {
    public static void main(String[] args) {
        Connection conn = null;
        try{
            // 代码底层有一个去查找的功能,查找资源路径下是否有该class类型
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/train2025?serverTimezone=Asia/Shanghai&useTimezone=true",
                    "root","123456");
            Statement statement = conn.createStatement();
            int i = statement.executeUpdate("delete from emp where deptno is null");
            System.out.println("受影响的条数:"+i);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                conn.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

 案例四:查询数据

查询员工的编号,姓名,职位,部门编号以及其领导姓名,职位

public class _04QueryDemo {
    public static void main(String[] args) {
        Connection conn = null;
        try{
            Class.forName("com.mysql.cj.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/train2025?serverTimezone=Asia/Shanghai&useTimezone=true",
                    "root","123456");
            Statement stat = conn.createStatement();
            // 执行查询时,返回的是ResultSet这个接口实现类对象
            ResultSet set = stat.executeQuery("select e.empno,e.ename,e.job,e.deptno,m.ename mname ,m.job mjob from emp m right join emp e on e.mgr = m.empno");
            /**
             * 结果集对象: 里面封装了查询结果,以及一个指针
             *    查询结果是一个二维表的形式,
             *    指针默认指向的是第一行的上面。
             * next(): 判断有没有下一行,如果有返回true,同时移动到下一行。
             *
             *  当指针指向某一行时,我们可以调用相关方法,来获取当前行的每一列的数据。
             *  getInt(int columnIndex)
             *  getInt(String columnName)
             *  同上:对应的方法都是
             *  getXXX(...) 两个重载方法:
             *     XXX:  java里的类型
             *
             *     对照表:
             *        数据库----------------java
             *        int                  int
             *        varchar/char/text    String
             *        double/float         double/float
             *        date                 Date
             *        boolean              Boolean
             */
            while(set.next()){
                 int empno = set.getInt(1);
                 String ename = set.getString(2);
                 String job = set.getString(3);
                 int deptno = set.getInt("deptno");
                 String mname = set.getString("mname");
                 String mjob = set.getString("mjob");
                 System.out.println(empno+","+ename+","+job+","+deptno+","+mname+","+mjob);
             }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                conn.close();
            } catch (SQLException e) {
               e.printStackTrace();
            }
        }
    }
}

 DBUtil工具类的封装

第一步:编写配置文件properties

#低版本的mysql的驱动和url
#driver=com.mysql.jdbc.Driver
#url=jdbc:mysql://localhost:3306/bd1906?useUnicode=true&characterEncoding=utf8


#高版本的mysql的驱动和url
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydb?serverTimezone=Asia/Shanghai&useTimezone=true
username=root
passwd=123456

第二步: 定义工具类DBUtil,读取配置文件,定义连接方法,关闭方法等

`/**
 * 封装了一些对数据库的连接和关闭操作
 */
public class DBUtil {
    private static String driver;
    private static String url;
    private static String username;
    private static String password;
    static{

        try{
            //读取配置文件:
            InputStream is = DBUtil.class.getClassLoader()
            .getResourceAsStream("db.properties");
            //获取Properties对象
            Properties p = new Properties();
            //把流里的内容加载到p对象中
            p.load(is);
            //取value值
            driver = p.getProperty("driver");
            url = p.getProperty("url");
            username = p.getProperty("username");
            password = p.getProperty("pwd");
            //注册驱动
            Class.forName(driver);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 连接数据库的方法
     * @return Connection对象
     */
    public static Connection getConnection() throws SQLException{
        Connection conn = DriverManager.getConnection(url,username,password);
        return conn;
    }

    /**
     * 关闭连接操作
     *  关闭ResultSet对象
     *  关闭Statement对象
     *  关闭Connection对象
     */
    public static void closeConnection(Connection conn, Statement stat, ResultSet rs){
        try {
            if(rs!=null){
                rs.close();
            }
            if(stat!=null){
                stat.close();
            }
            if(conn !=null){
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws SQLException {
        Connection conn = getConnection();
        System.out.println(conn);
        closeConnection(conn,null,null);
    }
}`

 第三步:调用DBUtil工具类,进行测试

1. update代码的修改

public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Connection conn = DBUtil.getConnection();
        //第三步:获取执行对象Statement
        Statement stat = conn.createStatement();
        String sql = "update emp set sal = 10000,comm = 500 where empno = 9000";
        //第四步:调用方法执行sql,即发送sql
        int num = stat.executeUpdate(sql);
        System.out.println("受影响的记录数目:"+num);
        //第五步:关闭连接
        DBUtil.closeConnection(conn,stat,null);
}

 2. DQL代码的修改

public void test1(){
        Connection conn = null;
        Statement stat = null;
        ResultSet rs = null;
        try{
            conn = DBUtil.getConnection();
            //获取执行对象,执行sql
            stat = conn.createStatement();
            //返回结果集
            rs = stat.executeQuery("select * from emp");
            while(rs.next()){
               int empno = rs.getInt(1);
               String ename = rs.getString("ename");
               Date hiredate = rs.getDate("hiredate");
               double salary = rs.getDouble("sal");
               System.out.println(empno+","+ename+","+hiredate+","+salary);
           }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
           DBUtil.closeConnection(conn,stat,rs);
        }
}`

具体案例:

DBUtil.java文件

public class DBUtil {
    public static String driverName;
    public static String url;
    public static String username;
    public static String passwd;

    // 使用IO流读取配置文件中的参数,给静态变量赋值。
    static {
        try {
            InputStream is = DBUtil.class.getClassLoader().getResourceAsStream("db.properties");
            //创建一个Properties对象
            Properties prop = new Properties();
            // load(): 传入一个字节流对象, 会将对应的文件中的=前的值作为key,=后的值作为value存储到Properties对象上
            prop.load(is);
            //从prop对象上获取对应的value值,给静态变量赋值即可
            driverName = prop.getProperty("driver");
            url = prop.getProperty("url");
            username = prop.getProperty("username");
            passwd = prop.getProperty("passwd");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 定义一个方法:用于获取与数据库的连接对象
     */
    public static Connection getConnection() {
        Connection conn = null;
        try {
            //注册驱动
            Class.forName(driverName);
            //获取连接
            conn = DriverManager.getConnection(url,
                    username, passwd);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //返回连接对象
        return conn;
    }

    /**
     * 定义一个方法: 用于关闭与数据库的连接操作
     */
    public static void closeConnection(Connection conn) {
        if (conn != null) {
            try {
                //传过来的对象不是null,那就关掉它。
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Connection connection = getConnection();
        System.out.println(connection);
        if (connection!=null){
            closeConnection(connection);
        }
    }
}

查询:员工的姓名,职位,工资,部门编号,部门名称,部门地址

public class UtilDemo01 {
    public static void main(String[] args) {
        Connection conn = null;
        try{
            //调用工具类里的连接方法,获取连接对象
            conn = DBUtil.getConnection();
            Statement statement = conn.createStatement();
            ResultSet rs = statement.executeQuery("select ename,job,sal,emp.deptno,dname,loc from emp join dept on emp.deptno = dept.deptno");
            while(rs.next()){
                String ename = rs.getString("ename");
                String job = rs.getString("job");
                double sal = rs.getDouble("sal");
                int deptno = rs.getInt("deptno");
                String dname = rs.getString("dname");
                String loc = rs.getString("loc");
                System.out.println(ename+","+job+","+sal+","+deptno+","+dname+","+loc);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            DBUtil.closeConnection(conn);
        }
    }
}

 JDBC的批处理

每一次的sql操作都会占用数据库的资源。如果将N条操作先存储到缓存区中,然后再一次性刷到数据库中,这就减少了与数据库的交互次数。因此可以提高效率。

Statement 提供了以下两个常用的方法,用做批处理

addBatch(String sql):将sql语句添加到缓存中
executeBatch():将缓存中的sql一次性刷到数据库中

 具体案例:

 向数据库中添加10w条记录:

insert into testbatch(id,name,gender) values (1,'zs','女');

普通写法:

public class _01TestBatchDemo {
    public static void main(String[] args) {
        Connection conn = null;
        try{
            conn = DBUtil.getConnection();
            Statement stat = conn.createStatement();
            long start = System.currentTimeMillis();
            for (int i = 2; i < 100000; i++) {
                stat.executeUpdate("insert into testbatch(id,name,gender) values ("+i+",'zs"+i+"','女')");
            }
            long end = System.currentTimeMillis();
            System.out.println(end-start);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            DBUtil.closeConnection(conn);
        }
    }
}

 使用批处理方法
  addBatch(String sql):将sql添加到缓存区中
  executeBatch():  将缓存区中的sql一次性冲刷到数据库

public class _02TestBatchDemo {
    public static void main(String[] args) {
        Connection conn = null;
        try{
            conn = DBUtil.getConnection();
            Statement stat = conn.createStatement();
            long start = System.currentTimeMillis();
            for (int i = 1; i <= 100001; i++) {
                //添加到缓存区中
                stat.addBatch("insert into testbatch(id,name,gender) values ("+i+",'zs"+i+"','女')");
                // 1000条一冲刷
                if(i%1000==0){
                    stat.executeBatch();
                }
            }
            //for循环结束后,缓存区中可能还有数据,需要冲刷一次
            stat.executeBatch();
            long end = System.currentTimeMillis();
            System.out.println(end-start);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            DBUtil.closeConnection(conn);
        }
    }
}

SQL 注入问题

1.需求:实现输入用户名和密码后,实现跳转到主页面的功能
2.逻辑分析:
    - 客户端:接收用户名和密码,并将这些信息发送到服务端
    - 服务端:接收到客户端传过来的用户名和密码后,进行数据库校验是否存在这样的数据,如果存在,就将
            用户名对应的这一条记录返回,并封装成一个User对象。返回给客户端。
    - 客户端收到返回信息后,判断Account对象是否存在,如果存在,就实现跳转.....

 

drop table if exists bank_account;
create table bank_account (
   id int primary key auto_increment comment '主键',
   account_id varchar(18) not null comment '用户账号',
   account_balance double(10,2) comment '账户余额',
   user_name varchar(20) not null comment '用户名称',
   user_pwd varchar(18) unique comment '用户密码',
   user_idcard varchar(18) unique comment '身份证',
   oper_time timestamp comment '操作日期',
   gender char(1) check(gender in('f','m')) comment 'f 表示女性,m表示男性'
);

insert into bank_account values (null,'6225113088436225',200000,'zhugeliang','zgl123456','100000100010101000','2019-01-01 13:10:10','m');
insert into bank_account values (null,'6225113088436226',1000,'zhouyu','zy123456','100000100010101001','2019-03-01 14:10:10','m');
insert into bank_account values (null,'6225113088436227',210000,'caocao','cc123456','100000100010101002','2019-04-01 14:10:10','m');
insert into bank_account values (null,'6225113088436228',500,'niumo','nm123456','100000100010101003','2019-03-01 10:10:10','m');

commit;
select * from banK_account;

select * from bank_account where user_name = 'zhugeliang' and user_pwd = '111111' or '1'='1'

Account类型的定义

 

package com.jdbc.day02.login;

import java.sql.Date;
import java.util.Objects;

/**
 * Account类型 对应bank_account表的字段。
 * 类型的属性对应表的字段
 * 这样做的目的是,可以把表中的每一条记录封装成一个Account对象
 */
public class Account {
    private int id;
    private String accountId;
    private double balance;
    private String username;
    private String password;
    private String idcard;
    private Date operationTime;
    private String gender;

    public Account(){}

    public Account(int id, String accountId, double balance, String username, String password, String idcard, Date operationTime, String gender) {
        this.id = id;
        this.accountId = accountId;
        this.balance = balance;
        this.username = username;
        this.password = password;
        this.idcard = idcard;
        this.operationTime = operationTime;
        this.gender = gender;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getAccountId() {
        return accountId;
    }

    public void setAccountId(String accountId) {
        this.accountId = accountId;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getIdcard() {
        return idcard;
    }

    public void setIdcard(String idcard) {
        this.idcard = idcard;
    }

    public Date getOperationTime() {
        return operationTime;
    }

    public void setOperationTime(Date operationTime) {
        this.operationTime = operationTime;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Account account = (Account) o;
        return id == account.id &&
                Double.compare(account.balance, balance) == 0 &&
                Objects.equals(accountId, account.accountId) &&
                Objects.equals(username, account.username) &&
                Objects.equals(password, account.password) &&
                Objects.equals(idcard, account.idcard) &&
                Objects.equals(operationTime, account.operationTime) &&
                Objects.equals(gender, account.gender);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, accountId, balance, username, password, idcard, operationTime, gender);
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", accountId='" + accountId + '\'' +
                ", balance=" + balance +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", idcard='" + idcard + '\'' +
                ", operationTime=" + operationTime +
                ", gender='" + gender + '\'' +
                '}';
    }
}

  安全漏洞演示:
      select * from bank_account  where user_name = 'zhugeliang' and user_pwd = '' or '1' = '1';

  SQL注入问题:
        黑客通过传入特殊的参数,来改变原有的SQL的结构 ,比如原有的SQl是两个条件,通过特殊的参数可以造成有三个条件的情况。

  该漏洞是Statement这个接口以及实现类的问题。 Statement可以改变SQL的结构,原因Statment发送sql到DBMS时,DBMS都会重新检验语法是否合理。
  并没有将SQL语法固定下来(固定指的是条件的个数固定,结构固定。)


 public class ClientDemo {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入用户名");
        String username = scanner.nextLine();
        System.out.println("请输入密码");
        String pwd = scanner.nextLine();

        //调用服务端的类型来创建一个服务端的对象
//        ServerDemo server = new ServerDemo();
        ServerDemo02 server = new ServerDemo02();
        boolean exists = server.isExists(username, pwd);
        if (exists){
            System.out.println("---登录成功,正在跳转----");
        }else{
            System.out.println("---登录失败,用户名或者密码不正确----");
        }
    }
}

 具体案例

/**
 * 定义一个简易服务端,封装一个方法,用来向数据库提交用户名和密码,检查用户名和密码是否匹配
 */
public class ServerDemo {
    /**
     *   将用户名和密码作为条件,来查询是否有这么一条记录。
     *
     * @param username   客户端传入的用户名
     * @param pwd        客户端传入的密码
     * @return    true,表示有这一条记录, false表示没有这一条记录
     */
    public boolean isExists(String username,String pwd){
        Connection conn = null;
        try{
            String sql = "select * from bank_account where user_name='"+ username+"' and user_pwd='"+pwd+"'";

            conn = DBUtil.getConnection();
            Statement stat = conn.createStatement();
            ResultSet rs = stat.executeQuery(sql);
            return  rs.next();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            DBUtil.closeConnection(conn);
        }
        return false;
    }
}

 /**
 * 定义一个简易服务端,封装一个方法,用来向数据库提交用户名和密码,检查用户名和密码是否匹配
 */
public class ServerDemo {
    /**
     *   将用户名和密码作为条件,来查询是否有这么一条记录。
     *
     * @param username   客户端传入的用户名
     * @param pwd        客户端传入的密码
     * @return    true,表示有这一条记录, false表示没有这一条记录
     */
    public boolean isExists(String username,String pwd){
        Connection conn = null;
        try{
            String sql = "select * from bank_account where user_name='"+ username+"' and user_pwd='"+pwd+"'";

            conn = DBUtil.getConnection();
            Statement stat = conn.createStatement();
            ResultSet rs = stat.executeQuery(sql);
            return  rs.next();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            DBUtil.closeConnection(conn);
        }
        return false;
    }
}

 

/**
 * 使用JDBC提供的另外一个语句对象: 预编译接口,PreparedStatement.
 *
 *  该接口的特点:
 *        1. 需要提前将SQl发送给DBMS,DBMS只会验证一次该SQL的语法,结构。
 *        2. 以后再次发送该SQL时,不会验证了,因为验证过了,结构语法都固定了,  因为减少了验证次数,因此执行效率就非常高。
 *        3. 发送的SQL可以使用占位符来给条件参数占位。再次发送时,将具体的参数发送给DBMS即可。
 *            占位符号:? ,  该问号可以代替java中的任何类型,在数据库底层会自动转成相应的类型,
 *            比如,  ?,可以代替java中的字符串,在数据库底层会转成数据库的char/varchar,自带单引号。
 *
 *            提供了setXXX方法给各种类型赋值
 *
 */
public class ServerDemo02 {
    public boolean isExists(String username,String pwd){
        Connection conn = null;
        try{
            String sql = "select * from bank_account where user_name=? and user_pwd=?";

            conn = DBUtil.getConnection();
            //Statement stat = conn.createStatement();  //有sql注入风险
            PreparedStatement prep = conn.prepareStatement(sql);//该方法会先将sql发送给DBMS,而DBMS在解析时,会认为该sql就是这种结构,比如只有两个条件。
            //给问号赋值,按照问号的顺序赋值即可,从1开始
            prep.setString(1,username);
            prep.setString(2,pwd);
            //再次发送,注意该次发送的只是给问号赋的值。DBMS就会自动将值放在问号处,执行SQL
            ResultSet rs = prep.executeQuery();
            return  rs.next();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            DBUtil.closeConnection(conn);
        }
        return false;
    }
}

银行转账案例演示

1.需求:一个账号fromAccount向另一个账号toAccount转入money元钱
2.分析:
    - 检查两个账号是否存在,不存在的话,结束转账行为
    - 检查转出账号的里金额是否充足,不充足,结束转账行为,充足的话,进行扣款money元
    - 转入账号进行增加money元


/**
 * 案例演示: 银行转账业务
 */
public class TransFerDemo {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        System.out.println("请输入转出账号 和 转入账号,使用空格隔开");
        String fromAccount = scan.next();
        String toAccount = scan.next();
        System.out.println("请输入要转出的金额");
        double money = scan.nextDouble();
        boolean b = transferAccount(fromAccount, toAccount, money);
        System.out.println(b);
    }

    /**
     * 实现转账逻辑:
     *      1. 先判断两个账号是否是正确的格式。
     *      2. 再判断需要转的金额是否正数。
     *      3. 判断转出账号是否在数据库中,如果在,查看余额是否够用
     *      4. 判断转入账号是否在数据库中,如果在,就可以进行累加金额了。
     * @param fromAccount
     * @param toAccount
     * @param money
     * @return
     */
    public static boolean transferAccount(String fromAccount,String toAccount,double money){
        if (fromAccount ==null || toAccount == null|| fromAccount ==""|| toAccount==""){
            System.out.println("--两个账号不能为空--");
            return false;
        }
        if (money <= 0){
            System.out.println("---金额不能为负数---");
            return false;
        }

        Connection conn = null;
        try{





            conn = DBUtil.getConnection();
            //  取消事务的自动提交操作,  取消后,从该处开始一直到手动提交,才是一个完整的事务。
            conn.setAutoCommit(false);


            //验证两个账号是否存在数据库中, 即通过账号查询是否存在相应的行数据即可
            PreparedStatement prep1 = conn.prepareStatement("select * from bank_account where account_id=?");
            prep1.setString(1,fromAccount);

            ResultSet rs1 = prep1.executeQuery();
            if (!rs1.next()){
                System.out.println("---转出账号有误---");
                return false;
            }
            //获取转出账号的余额
            double accountBalance = rs1.getDouble("account_balance");
            if (accountBalance<money){
                System.out.println("---余额不足---");
                return false;
            }
            // 只要给?赋值过,那么之前的sql语句就不能再次使用和赋值操作了。
            PreparedStatement prep2 = conn.prepareStatement("select * from bank_account where account_id=?");
            prep2.setString(1,toAccount);
            ResultSet rs2 = prep2.executeQuery();
            if (!rs2.next()){
                System.out.println("---转入账号有误---");
                return false;
            }
            //就可以进行转账操作了
            PreparedStatement prep3 =
                    conn.prepareStatement("update bank_account set account_balance = account_balance - ? where account_id = ? ");
            prep3.setDouble(1,money);
            prep3.setString(2,fromAccount);
            prep3.executeUpdate();  // 转出完成

            /*在该处模拟异常*/
            String str = null;
            System.out.println(str.length());

            PreparedStatement prep4 =
                    conn.prepareStatement("update bank_account set account_balance = account_balance + ? where account_id = ? ");
            prep4.setDouble(1,money);
            prep4.setString(2,toAccount);
            prep4.executeUpdate();  // 转入完成

            // 入账成功后,再提交才是合理的。整个转账业务应该属于一个事务
            conn.commit();

            return true;
        }catch (Exception e){
            e.printStackTrace();
            //进行回滚操作
            try {
                conn.rollback();
            } catch (SQLException ex) {
                e.printStackTrace();
            }
        }finally {
            DBUtil.closeConnection(conn);
        }
        return false;
    }
}

 转账异常演示:

在转出账户转出金额之后 和  转入账户收入金额之前模拟 空指针异常

String str = null;
System.out.println(str.length());

 JDBC的事务支持

事务的概念

当一个业务需求涉及到N个DML操作时,这个业务(或者时N个DML操作)当成一个整体来处理。在处理的过程中,如果有失败或异常,我们要回到业务开始时。如果成功处理,我们再将数据持久化到磁盘中。这样一个过程我们称之为一个事务。具有原子性。不可切割。


关键字:
commit
rollback
savepoint

事务的特性

JDBC(Java Database Connectivity)的事务支持遵循了ACID原则,这是一组确保数据库事务可靠性的特性。ACID包括:

  1. 原子性(Atomicity)

    • 事务是一个不可分割的操作序列,要么全部执行成功,要么全部不执行。如果事务中的某个操作失败,整个事务都会被回滚到初始状态。
  2. 一致性(Consistency)

    • 事务必须使数据库从一个一致性状态转变到另一个一致性状态。在事务开始之前和完成之后,数据库的完整性约束必须得到满足。
  3. 隔离性(Isolation)

    • 并发执行的事务之间互不干扰。一个事务的执行不应该受到其他事务的影响。JDBC支持不同级别的隔离,以控制事务之间的可见性(如READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE等)。
  4. 持久性(Durability)

    • 一旦事务提交,所有的更改都会永久保存到数据库中,即使系统崩溃也不会丢失。这意味着提交后的数据修改是持久的。

在使用JDBC时,开发者可以通过设置事务的隔离级别、使用commit()rollback()方法来管理事务,从而确保这些特性得到遵循。

 MySQL事务

- 默认情况下,MySQL每执行一条SQL语句,都是一个单独的事务。
- 如果需要在一个事务中包含多条SQL语句,那么需要开启事务和结束事务。
    开启事务:start transaction;
    结束事务:commit或rollback;

 回滚情况

START TRANSACTION;
UPDATE account SET balance=balance-10000 WHERE id=1;
SELECT * FROM account;
UPDATE account SET balance=balance+10000 WHERE id=2;
ROLLBACK;

 提交情况

START TRANSACTION;
UPDATE account SET balance=balance-10000 WHERE id=1;
SELECT * FROM account;
UPDATE account SET balance=balance+10000 WHERE id=2;
COMMIT;

 JDBC的事务支持

Connection.setAutoCommit(boolean flag):此方法可以取消事务的自动提交功能,值为false。  
Connection.commit():进行事务提交   。  
Connection.rollback():进行事务回滚。  

 多事务情况

在JDBC中处理多事务情况时,需要特别注意事务的管理和隔离性,以确保数据的一致性和完整性。以下是一些处理多事务情况时的关键点:

1. 事务隔离级别

JDBC允许设置不同的事务隔离级别,这对于多事务环境至关重要。常见的隔离级别包括:

  • READ UNCOMMITTED:允许读取未提交的事务数据,可能会导致脏读。
  • READ COMMITTED:只允许读取已提交的数据,避免脏读,但可能会出现不可重复读。
  • REPEATABLE READ:保证在同一事务中多次读取相同的数据结果相同,避免不可重复读,但可能会出现幻读。
  • SERIALIZABLE:最高的隔离级别,完全隔离事务,避免幻读、不可重复读和脏读,但性能可能会受到影响。

可以使用 Connection.setTransactionIsolation(int level) 来设置事务的隔离级别。

2. 事务管理

  • 显式管理:使用 Connection.setAutoCommit(false) 来显式关闭自动提交模式,然后手动控制提交 (commit()) 和回滚 (rollback())。
  • 嵌套事务:JDBC不直接支持嵌套事务,但可以通过保存点(Savepoints)来实现部分回滚。使用 Connection.setSavepoint() 创建保存点通过 Connection.rollback(Savepoint) 回滚到特定的保存点。

3. 并发控制

在多事务环境中,可能会发生并发冲突。可以考虑使用以下策略:

  • 乐观锁:在更新数据之前不加锁,而是在提交时检查数据的版本或其他条件,以确保没有其他事务已更改数据。
  • 悲观锁:在读取数据时加锁,确保其他事务无法修改这些数据,直到当前事务完成。

4. 处理死锁

在高并发的情况下,死锁是一个常见的问题。可以采取一些措施来避免死锁,例如:

  • 确保所有事务访问资源的顺序相同。
  • 设置合理的超时时间,避免长时间等待。

5. 同一连接中的多个事务

在同一连接中,可以依次处理多个事务,但需要注意控制事务的开始和结束。如果一个事务未提交,另一个事务将受到影响,尤其是在设置为非自动提交模式时。

6. 连接池和多线程环境

在使用连接池的应用程序中,每个线程可能会获取到不同的连接,因此要确保每个连接的事务管理是独立的,以避免事务混乱。

Connection conn = null;  
try {  
    conn = DriverManager.getConnection(url, user, password);  
    conn.setAutoCommit(false); // 关闭自动提交  

    // 执行多个操作  
    Statement stmt = conn.createStatement();  
    stmt.executeUpdate("INSERT INTO table1 ...");  
    stmt.executeUpdate("INSERT INTO table2 ...");  

    // 提交事务  
    conn.commit();  
} catch (SQLException e) {  
    if (conn != null) {  
        try {  
            conn.rollback(); // 出现异常时回滚  
        } catch (SQLException ex) {  
            ex.printStackTrace();  
        }  
    }  
    e.printStackTrace();  
} finally {  
    if (conn != null) {  
        try {  
            conn.close();  
        } catch (SQLException e) {  
            e.printStackTrace();  
        }  
    }  
}  

隔离机制

 

JDBC的隔离机制涉及到如何管理多个事务之间的相互影响,以确保数据的一致性和完整性。JDBC遵循SQL标准,支持四个主要的事务隔离级别。每个级别定义了不同程度的事务隔离,具体说明如下:

1. 隔离级别概述

  • READ UNCOMMITTED(未提交读)

    • 允许事务读取未提交的数据。这种级别可能导致脏读现象,即一个事务可以读取到另一个事务未提交的数据。适用于对实时性要求不高但能接受脏读的场景。
  • READ COMMITTED(已提交读)

    • 事务只能读取已提交的数据,避免脏读。这是大多数数据库默认的隔离级别。虽然可以避免脏读,但可能产生不可重复读,也就是当数据在同一事务内被多次读取时,数据可能发生变化。
  • REPEATABLE READ(可重复读)

    • 确保在同一事务中多次读取相同的数据时得到的是相同的结果。这一隔离级别防止了脏读和不可重复读,但可能产生幻读,即在同一事务期间,新增的行在之后的查询中可见。
  • SERIALIZABLE(可串行化)

    • 提供最高级别的隔离,确保事务交替处理。它防止了脏读、不可重复读和幻读,但性能相对较低,因为它通常通过加锁机制来实现完全的隔离。

2. 设置和使用隔离级别

在JDBC中,你可以通过 Connection.setTransactionIsolation(int level) 方法来设置不同的事务隔离级别。

import java.sql.Connection;  
import java.sql.DriverManager;  
import java.sql.SQLException;  

public class IsolationLevelExample {  

    public static void main(String[] args) {  
        String url = "jdbc:your_database_url";  
        String user = "your_username";  
        String password = "your_password";  

        try (Connection conn = DriverManager.getConnection(url, user, password)) {  
            // 设置事务隔离级别  
            conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);  

            // 开始事务         
            conn.setAutoCommit(false);  
            
            // 执行一些数据库操作  
            
            conn.commit(); // 提交事务  
        } catch (SQLException e) {  
            e.printStackTrace();  
        }  
    }  
}  

3. 选择隔离级别的考虑因素

选择适当的隔离级别时,需要考虑以下因素:

  • 数据一致性需求:如果应用程序对数据一致性要求高,应该选择更高的隔离级别,如可串行化。
  • 性能需求:较低的隔离级别(如未提交读)提高了并发性,但可能导致数据不一致。根据应用性质来平衡性能和一致性。
  • 并发量:高并发环境中,如果每个事务都需要较高的隔离级别,可能会导致性能下降、死锁频发。

4. 注意事项

  • 隔离级别的提高通常会增加锁的争用,降低系统的并发性。因此,在选择隔离级别时,开发者需仔细权衡性能和数据一致性。
  • 并非所有的数据库都支持所有的隔离级别,某些级别可能会因数据库的实现而有所不同。

 set session transaction isolation level read uncommitted;
set session transaction isolation level read committed;
set session transaction isolation level repeatable read;
set session transaction isolation level read uncommitted;


查询mysql的当前会话的隔离机制:
SELECT @@transaction_isolation;


修改全局的隔离机制: 新会话生效

SET GLOBAL transaction_isolation = 'READ-COMMITTED';

 修改转账代码:改为手动提交

public static boolean oneToOne(String fromAccount, String toAccount, double money) {
    /*第一步:先校验账户信息是否有效*/
    if (fromAccount == null || fromAccount.length() == 0) {
        return false;
    }
    if (toAccount == null || toAccount.length() == 0) {
        return false;
    }
    if (money <= 0) {
        return false;
    }
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
        conn = DBUtil.getConnection();
        
        //取消事务自动提交功能,改为手动提交
        conn.setAutoCommit(false);
        
        String sql1 = "select * from bank_account where account_id=?";
        ps = conn.prepareStatement(sql1);
        ps.setString(1, fromAccount);
        rs = ps.executeQuery();
        boolean flag = rs.next();
        if (!flag) {
            System.out.println("出账账号不存在");
            return false;
        }
        /*第二步:检查formAccount账户内的余额,足够的前提下,修改余额*/
        //取出余额
        double balance = rs.getDouble("account_balance");

        //sql1 = "select * from bank_account where account_id=?";
        //ps = conn.prepareStatement(sql1);
        ps.setString(1, toAccount);
        rs = ps.executeQuery();
        flag = rs.next();
        if (!flag) {
            System.out.println("进账账号不存在");
            return false;
        }
		
        //判断出账账号的余额是否充足
        if (money > balance) {
            System.out.println("余额不足");
            return false;
        }

        //此时,来到此处后就可以进行转账业务了
        //先扣除出账账号的money
        String sql2 = "update bank_account set account_balance=account_balance-? where account_id = ?";
        ps = conn.prepareStatement(sql2);
        ps.setDouble(1, money);
        ps.setString(2, fromAccount);
        ps.executeUpdate();

        //模拟一个空指针异常
        String str = null;
        System.out.println(str.length());
		/*第三步:修改toAccount账户内的余额*/
        //进账账号再+money
        String sql3 = "update bank_account set account_balance=account_balance+? where account_id = ?";
        ps = conn.prepareStatement(sql3);
        ps.setDouble(1, money);
        ps.setString(2, toAccount);
        ps.executeUpdate();

        //手动提交事务
        conn.commit();
        return true;
    } catch (Exception e) {
        e.printStackTrace();
        //当出现异常,我们才需要回滚事务
        try{
            conn.rollback();
        }catch (Exception e1){
            e1.printStackTrace();
        }
    } finally {
        DBUtil.closeConnection(conn, ps, rs);
    }
    return false;
}

 数据库连接池技术

在与数据库连接过程中,会非常消耗内存,性能大打折扣。如果每次请求都去重新连接数据库。那么,宕机的几率很高。

因此,我们可以使用连接池技术。

连接池的工作原理:

连接池对象在初始化阶段 一次性创建N个连接对象,这些连接对象存储在连接池对象中。当有请求过来时,先从连接池中寻找空闲连接对象并使用,当使用完后,将连接对象归还给连接池,而不是真正意义上断开连接。这样也可以满足成千上万个请求,同时并提高了数据库的性能。


常用的连接池技术:

- dbcp        :是apache组织旗下的一个数据库连接池技术产品
- c3p0        :是一个开源的连接池技术
- druid        :是阿里的数据库连接池技术

 dbcp

资源jar包:

commons-dbcp2-2.6.0.jar
commons-pool2-2.4.3.jar
commons-logging.jar
数据库连接jar包:mysql-connector-java-8.0.19.jar

 配置文件dbcp.properties:

此配置文件请放在src目录下

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/bd1906
username=root
pwd=123456
# 初始化的连接数量
initialSize=5
#最大的连接数量
maxTotal=50
# 最大的空闲数量。 当创建临时Connection时,在未超时的时候,可以留下来几个,继续等待被征用。
maxIdle=10
# 最小的空闲数量。 当创建临时Connection时,在未超时的时候,可以留下来几个,继续等待被征用。
minIdle=3
# 超时时间,给临时会话用的
maxWaitMillis=60000

 DBUtilddbcp类型的编写:

import org.apache.commons.dbcp2.BasicDataSource;

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

/**
 * 使用DBCP连接池对象
 */
public class DBUtildbcp {
    private static String driver;
    private static String url;
    private static String username;
    private static String password;
    private static int maxTotal;
    private static int maxIdle;
    private static int minIdle;
    private static int initialSize;
    private static long maxWaitMillis;

    //声明一个dbcp连接池变量
    private static BasicDataSource pool;
    static{
        try{
            pool = new BasicDataSource();//连接池对象

            //使用类加载器中提供的方法来获取字节流对象,同时指定配置文件
            InputStream is = DBUtildbcp.class.getClassLoader()
                .getResourceAsStream("db.properties");
            Properties prop = new Properties();
            prop.load(is);//将配置文件里的内容封装到prop对象内

            driver = prop.getProperty("driver");
            url = prop.getProperty("url");
            username = prop.getProperty("username");
            password = prop.getProperty("pwd");
            maxTotal= Integer.parseInt(prop.getProperty("maxTotal"));
            maxIdle= Integer.parseInt(prop.getProperty("maxIdle"));
            minIdle= Integer.parseInt(prop.getProperty("minIdle"));
            initialSize= Integer.parseInt(prop.getProperty("initialSize"));
            maxWaitMillis= Long.parseLong(prop.getProperty("maxWaitMillis"));

            pool.setDriverClassName(driver);
            pool.setUrl(url);
            pool.setUsername(username);
            pool.setPassword(password);
            //连接池支持的最大连接数
            pool.setMaxTotal(maxTotal);
            //连接池支持的最大空闲数
            pool.setMaxIdle(maxIdle);
            //支持的最小空闲数
            pool.setMinIdle(minIdle);
            //连接池对象创建时初始化的连接数
            pool.setInitialSize(initialSize);
            //空闲等待时间
            pool.setMaxWaitMillis(maxWaitMillis);
            //注册驱动
            Class.forName(driver);

        }catch (Exception e){
            e.printStackTrace();
        }
    }
    public static Connection getConnection() throws SQLException {
        //从连接池中获取空闲对象
        return pool.getConnection();
    }
    public static void closeConnection(Connection conn, Statement stat, ResultSet rs){
        try {
            if(rs!=null){
                rs.close();
            }
            if(stat!=null){
                stat.close();
            }
            if(conn !=null){
                conn.close();   //会将连接对象归还给连接池内
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws SQLException {
        Connection conn = getConnection();
        System.out.println(conn);
        conn.close();
    }
}

 c3p0

资源jar包:

c3p0-0.9.5-pre8.jar
mchange-commons-java-0.2.7.jar

 配置文件c3p0-config.xml:

配置文件请放在src目录下

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <default-config>
        <property name="user">root</property>
        <property name="password">123456</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/bd1901</property>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <!--  一次性增加的连接个数 -->
        <property name="acquireIncrement">10</property>
        <property name="maxPoolSize">50</property>
        <property name="minPoolSize">2</property>
        <property name="initialPoolSize">5</property>
        <property name="maxIdleTime">600</property>
    </default-config>
</c3p0-config>

 DBUtilc3p0类型的编写:

import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class DBUtilC3p0 {
    //构造器会自动检索src下有没有指定文件名称的配置文件,然后会自动赋值给其相应的属性
    private static ComboPooledDataSource pool = new ComboPooledDataSource("c3p0-config");

    public static Connection getConnection() throws SQLException {
        //从连接池中获取空闲对象
        return pool.getConnection();
    }
    public static void closeConnection(Connection conn, Statement stat, ResultSet rs){
        try {
            if(rs!=null){
                rs.close();
            }
            if(stat!=null){
                stat.close();
            }
            if(conn !=null){
                conn.close();   //会将连接对象归还给连接池内
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws SQLException {
        Connection conn = getConnection();
        System.out.println(conn);
        conn.close();

    }
}

 druid

资源jar包:

druid-1.1.18.jar

 配置文件druid.properties:

放在src目录下。注意,前面的key值是固定写法

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/bd1901
username=root
password=123456
maxActive=20
minIdle=3
initialSize=5
maxWait=60000

 DBUtildruid类型的编写:

import java.io.InputStream;
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;
public class DBUtil_druid {
    //创建连接池对象
    private static DataSource pool = null;
    static {
        try {
            //使用类加载器提供的方法读取db.properties,返回一个字节流对象
            InputStream is = DBUtil_druid.class.getClassLoader().
                getResourceAsStream("druid.properties");
            //创建Properties对象,用于加载流内部的数据
            Properties prop = new Properties();
            prop.load(is); //加载流内部的信息,以key-value的形式进行加载
            //调用静态方法,会自动给自己的属性赋值
            pool = DruidDataSourceFactory.createDataSource(prop);    
        } catch (Exception e) {
            System.out.println("注册驱动失败");
            e.printStackTrace();
        }
    }
    /**
     * 获取连接对象
     *
     * @return 连接对象
     * @throws SQLException
     * @throws ClassNotFoundException
     */
    public static Connection getConnection() throws SQLException, 
    ClassNotFoundException {

        //return DriverManager.getConnection(url, username, password);
        //从连接池中获取连接对象
        return pool.getConnection();
    }

    /**
     * 关闭数据库连接
     *
     * @param rs   结果集对象
     * @param stat 处理sql的执行对象Statement
     * @param conn 连接对象
     */
    public static void closeConnection(ResultSet rs, Statement stat, Connection conn) {
        try {
            if (rs != null) {
                rs.close();
            }
            if (stat != null) {
                stat.close();
            }
            if (conn != null) {
                conn.close();//释放连接,归还给连接池
            }
        } catch (Exception e) {
            System.out.println("数据库连接关闭失败");
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Connection conn = getConnection();
        System.out.println(conn);
        closeConnection(null, null, conn);
    }
}

 DAO设计模式

DAO简介

- DAO是数据访问对象(Data Access Object)的简写。
- 建立在数据库与业务层之间,封装所有对数据库的访问操作,我们也可称之为持久层。
- 目的: 将数据访问逻辑和业务逻辑分开。

 一个DAO设计模式包含以下内容

  1. 定义实体类: 通过对象关系映射(ORM)将数据库的表结构映射成java类型;表中的每一条记录映射成类的实例。用于数据的传 递。

  2. 定义一个接口: 在此接口中,定义应用程序对此表的所有访问操作,如增,删,改、查,等方法。

  3. 定义接口的实现类 实现接口中的所有抽象方法。

  4. 定义一个DAO工厂类型 用于返回接口实例 这样,开发人员只需要使用DAO接口即可,具体逻辑就变得透明了,无需了解内部细节。

扩展:项目的包名命名规则

规范: com.域名.项目名称.模块名称.具体分类
com.youcai.empmanager.login
com.youcai.empmanager.login.entity/vo/pojo:   封装一堆实体类
com.youcai.empmanager.login.test
com.youcai.empmanager.login.dao
com.youcai.empmanager.login.dao.impl
com.youcai.empmanager.login.service

DAO案例

DAO:
 1. 是业务层与数据库之间的一个代码层,我们称之为DAO层或者持久层。
 2. 这么设计的目的,是将业务逻辑代码和与数据库的访问逻辑进行解耦,将访问逻辑单独封装到DAO层。
 3. 如何设计DAO层:
     --1) 通过对象关系映射模型(ORM):  表结构-->类结构,  表记录-->类的实例
     --2) 定义接口,设计增删改查的抽象方法,即定义规范,如何与数据库进行交互。
     --3) 定义实现类(即:确定数据库,编写具体逻辑代码)
     --4) 定义DAO工厂:提供一个方法获取实现类对象

第一步:通过表结构,来定义类结构, (实体类型)

 1.表中的数字类型---->最好映射成java中的包装类,因为可以设置0和null。  基本数据类型不能设置成null.
 2.类中的属性名可以和表中的字段一模一样,也可以不一样.
              数据库中:表中的字段如果是两个单词,一般都是使用下划线连接。
              java中: 小驼峰命名法

package com.jdbc.empmanager.entity;

import java.sql.Date;
import java.sql.Timestamp;
import java.util.Objects;


public class Emp {
    private Integer empno;
    private String ename;
    private String job;
    private Integer mgr;
    private Date hiredate;   //   数据库中是Date类型,java就选择java.sql.Date       数据库中Datetime, java选Date
                             //   数据库中是Timestamp, java就选择java.sql.Timestamp
    private Double salary;
    private Double bonus;
    private Integer deptno;

    public Emp(){}

    public Emp(Integer empno, String ename, String job, Integer mgr, Date hiredate, Double salary, Double bonus, Integer deptno) {
        this.empno = empno;
        this.ename = ename;
        this.job = job;
        this.mgr = mgr;
        this.hiredate = hiredate;
        this.salary = salary;
        this.bonus = bonus;
        this.deptno = deptno;
    }

    public Integer getEmpno() {
        return empno;
    }

    public void setEmpno(Integer empno) {
        this.empno = empno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    public Integer getMgr() {
        return mgr;
    }

    public void setMgr(Integer mgr) {
        this.mgr = mgr;
    }

    public Date getHiredate() {
        return hiredate;
    }

    public void setHiredate(Date hiredate) {
        this.hiredate = hiredate;
    }

    public Double getSalary() {
        return salary;
    }

    public void setSalary(Double salary) {
        this.salary = salary;
    }

    public Double getBonus() {
        return bonus;
    }

    public void setBonus(Double bonus) {
        this.bonus = bonus;
    }

    public Integer getDeptno() {
        return deptno;
    }

    public void setDeptno(Integer deptno) {
        this.deptno = deptno;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Emp emp = (Emp) o;
        return Objects.equals(empno, emp.empno) && Objects.equals(ename, emp.ename) && Objects.equals(job, emp.job) && Objects.equals(mgr, emp.mgr) && Objects.equals(hiredate, emp.hiredate) && Objects.equals(salary, emp.salary) && Objects.equals(bonus, emp.bonus) && Objects.equals(deptno, emp.deptno);
    }

    @Override
    public int hashCode() {
        return Objects.hash(empno, ename, job, mgr, hiredate, salary, bonus, deptno);
    }

    @Override
    public String toString() {
        return "Emp{" +
                "empno=" + empno +
                ", ename='" + ename + '\'' +
                ", job='" + job + '\'' +
                ", mgr=" + mgr +
                ", hiredate=" + hiredate +
                ", salary=" + salary +
                ", bonus=" + bonus +
                ", deptno=" + deptno +
                '}';
    }
}

 第二步: 定义实体类对应的接口,在里面定义与数据库交换的操作规范,CRUD。

小贴士:  接口所在的包名 可以叫dao,也可以叫mapper
                接口名,一般都是实体类名+包名
           com.youcai.empmanger.emp.entity|vo|pojo.Emp
           com.youcai.empmanger.emp.dao|mapper.EmpDao|EmpMapper


package com.jdbc.empmanager.dao;

import com.jdbc.empmanager.entity.Emp;

import java.sql.SQLException;
import java.util.List;

public interface EmpDao {
    /**
     * 定义一个将员工对象插入到数据库中的方法:
     * 因为:要将对象插入到数据库中,因此该方法带参数,这样到底是哪个对象,非常灵活
     */
    void addEmp(Emp emp);

    /**
     * 修改方法: 用于将对象的新信息设置到数据库中
     * @param emp   参数是具有新信息的对象。
     */
    void modEmp(Emp emp);

    /**
     * 删除方法:  通过员工的唯一标识进行删除
     * @param empno   员工编号
     */
    void delEmp(int empno);

    /**
     * 查找方法:  通过员工的唯一标识进行查询,返回某一个员工的信息
     * @param empno   员工编号 ,有唯一性
     * @return    员工信息封装成的Emp对象
     */
    Emp findByEmpno(int empno) throws SQLException;

    /**
     * 查找方法: 查询表中的所有记录
     * @return   集合对象,里面存储了每一个员工对象
     */
    List<Emp> findAll();
}

第三步:开发过程中,确定了数据库,然后编写接口的具体的实现类逻辑。

 小贴士:  实现类一般都放在接口所在包的子包中。
          类名一般都与其要实现的接口有关系。


public class EmpDaoImpl implements EmpDao {
    @Override
    public void addEmp(Emp emp) {
        Connection conn = null;
        try{
            //从连接池中获取一个连接对象
            conn = DruidUtil.getConnection();
            PreparedStatement ps = conn.prepareStatement("insert into emp values(?,?,?,?,?,?,?,?)");
            ps.setInt(1,emp.getEmpno());
            ps.setString(2,emp.getEname());
            ps.setString(3,emp.getJob());
            ps.setInt(4,emp.getMgr());
            ps.setDate(5,emp.getHiredate());
            ps.setDouble(6,emp.getSalary());
            ps.setDouble(7,emp.getBonus());
            ps.setInt(8,emp.getDeptno());

            ps.executeUpdate();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            DruidUtil.closeConnection(conn);
        }
    }

    @Override
    public void modEmp(Emp emp) {
        Connection conn = null;
        try{
            //从连接池中获取一个连接对象
            conn = DruidUtil.getConnection();
            String sql = "update emp set ename = ?,job = ?,mgr=?,hiredate=?,sal=?,comm=?,deptno = ? where empno = ?";
            PreparedStatement ps = conn.prepareStatement(sql);
            //给问号赋值
            ps.setString(1,emp.getEname());
            ps.setString(2,emp.getJob());
            ps.setInt(3,emp.getMgr());
            ps.setDate(4,emp.getHiredate());
            ps.setDouble(5,emp.getSalary());
            ps.setDouble(6,emp.getBonus());
            ps.setInt(7,emp.getDeptno());
            ps.setInt(8,emp.getEmpno());

            //再次发送
            ps.executeUpdate();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            DruidUtil.closeConnection(conn);
        }
    }

    @Override
    public void delEmp(int empno) {
        Connection conn = null;
        try{
            //从连接池中获取一个连接对象
            conn = DruidUtil.getConnection();
            PreparedStatement ps = conn.prepareStatement("delete from emp where empno = ?");
            ps.setInt(1,empno);

            ps.executeUpdate();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            DruidUtil.closeConnection(conn);
        }
    }

    @Override
    public Emp findByEmpno(int empno) {
        Emp emp = null;
        Connection conn = null;
        try{
            //从连接池中获取一个连接对象
            conn = DruidUtil.getConnection();
            PreparedStatement ps = conn.prepareStatement("select * from emp where empno = ?");
            ps.setInt(1,empno);

            ResultSet rs = ps.executeQuery();
            if (rs.next()){
                String ename = rs.getString("ename");
                String job = rs.getString("job");
                int mgr = rs.getInt("mgr");
                Date hiredate = rs.getDate("hiredate");
                double salary = rs.getDouble("sal");
                double bonus = rs.getDouble("comm");
                int deptno = rs.getInt("deptno");
                //给emp赋值
                emp = new Emp(empno,ename,job,mgr, hiredate,salary,bonus,deptno);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            DruidUtil.closeConnection(conn);
        }
        return emp;
    }

    @Override
    public List<Emp> findAll() {
        List<Emp> emps = new ArrayList<>();

        Connection conn = null;
        try{
            //从连接池中获取一个连接对象
            conn = DruidUtil.getConnection();
            PreparedStatement ps = conn.prepareStatement("select * from emp");

            ResultSet rs = ps.executeQuery();
            while (rs.next()){
                int empno = rs.getInt("empno");
                String ename = rs.getString("ename");
                String job = rs.getString("job");
                int mgr = rs.getInt("mgr");
                Date hiredate = rs.getDate("hiredate");
                double salary = rs.getDouble("sal");
                double bonus = rs.getDouble("comm");
                int deptno = rs.getInt("deptno");
                //给emp赋值
                Emp emp = new Emp(empno,ename,job,mgr, hiredate,salary,bonus,deptno);
                //将该emp对象添加到集合中
                emps.add(emp);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            DruidUtil.closeConnection(conn);
        }
        return emps;
    }
}

 第四步:DAO设计模式定义一个工厂类型。(单例模式)

    为什么要这么做:  为了以后修改代码方便。
     如何就方便了呢?  因为别的很多地方都调用了工厂里的静态方法,
     而我们在换数据库时,只需要修改静态方法里的逻辑即可,修改了一处。  其他地方不用再次修改了。


package com.jdbc.empmanager.util;

import com.jdbc.empmanager.dao.EmpDao;
import com.jdbc.empmanager.dao.impl.EmpDaoImpl;
import com.jdbc.empmanager.dao.impl.EmpDaoImplOracle;


public class EmpDaoFactory {
    private static EmpDao instance;

    private EmpDaoFactory(){}
    /**
     * 定义一个静态方法,获取实现MySQL数据库的实现类对象
     */
    public static synchronized EmpDao getInstance(){
         if (instance == null){
             instance =  new EmpDaoImpl();
//             instance = new EmpDaoImplOracle();
         }
         return instance;
    }
}

 第五步:测试

package com.jdbc.empmanager.test;

import com.jdbc.empmanager.dao.EmpDao;
import com.jdbc.empmanager.dao.impl.EmpDaoImpl;
import com.jdbc.empmanager.dao.impl.EmpDaoImplOracle;
import com.jdbc.empmanager.entity.Emp;
import com.jdbc.empmanager.util.EmpDaoFactory;
import org.junit.Test;

import java.sql.Date;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.List;

public class TestDao {
    @Test
    public void testFindAll(){
        EmpDao dao = EmpDaoFactory.getInstance();
        List<Emp> all = dao.findAll();
        all.forEach(System.out::println);
    }

    @Test
    public void testDeleteEmp(){
        EmpDao dao = EmpDaoFactory.getInstance();
        dao.delEmp(10004);
    }


    @Test
    public void testAddEmp(){
        EmpDao dao = EmpDaoFactory.getInstance();
        Emp emp = new Emp(10004,"lucy","clerk",7369,
                Date.valueOf("2020-10-10"),2000.0,10.0,10);
        dao.addEmp(emp);
    }

    @Test
    public void testFindByEmpno() {
        EmpDao dao = EmpDaoFactory.getInstance();
        Emp emp = null;
        try {
            emp = dao.findByEmpno(7369);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        System.out.println(emp);
    }

    @Test
    public  void testModEmp() {
        EmpDao dao = EmpDaoFactory.getInstance();
        //查询员工信息
        Emp emp = new Emp(10003,"张老三","clerk",7369,
                Date.valueOf("2020-10-10"),2000.0,10.0,10);
        //将数据设置到数据库中
        dao.modEmp(emp);
    }
}

 dbutils第三方工具类的使用

- 此工具封装了DAO层(持久层)的逻辑。减少了开发周期。
- jar包:commons-dbutils-1.7.jar
- 常用API: 
1. QueryRunner类型:可以直接使用连接池技术来操作数据库,进行增删改查
   构造器:QueryRunner(DataSource ds)
             返回一个指定数据库连接池得QueryRunner对象
   非静态方法:query(String sql, ResultSetHandler<T> rsh)
            通过sql,及其ReusltSetHandler的子类型来获取数据并封装成相应对象
2. ResultSetHandler:关于结果集的一个接口。
    其实现类如下:
    BeanHandler:将查询到的数据的第一条封装成实体类对象
    BeanListHandler:将查询到的数据的第一条封装成实体类对象的集合

 继续刚才的DAO案例

借助第三方框架 dbutils,来简化JDBC的编写。
 1. 主要的类型: QueryRunner。  该类型提供了增删改查等方法,可以直接对数据库进行操作
    如何获取该类型的对象呢? 只需要调用构造器,传入一个连接池对象即可


EmpDaoImplOracle:

package com.jdbc.empmanager.dao.impl;

import com.jdbc.empmanager.dao.EmpDao;
import com.jdbc.empmanager.entity.Emp;

import java.util.Collections;
import java.util.List;

public class EmpDaoImplOracle implements EmpDao {
    @Override
    public void addEmp(Emp emp) {

    }

    @Override
    public void modEmp(Emp emp) {

    }

    @Override
    public void delEmp(int empno) {

    }

    @Override
    public Emp findByEmpno(int empno) {
        return null;
    }

    @Override
    public List<Emp> findAll() {
        return Collections.emptyList();
    }
}

EmpDaoImplOther:

package com.jdbc.empmanager.dao.impl;

import com.jdbc.empmanager.dao.EmpDao;
import com.jdbc.empmanager.entity.Emp;
import com.jdbc.util.DruidUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import java.sql.SQLException;
import java.util.Collections;
import java.util.List;


public class EmpDaoImplOther implements EmpDao {
    //定义成属性,这样的话,每个方法中都可以直接访问属性了
    private QueryRunner  qr;

    public EmpDaoImplOther(){
        qr = new QueryRunner(DruidUtil.pool);
    }

    @Override
    public void addEmp(Emp emp) {
        try {
            qr.insert("insert into emp values(?,?,?,?,?,?,?,?)",new BeanHandler<Emp>(Emp.class),
                    emp.getEmpno(),
                    emp.getEname(),
                    emp.getJob(),
                    emp.getMgr(),
                    emp.getHiredate(),
                    emp.getSalary(),
                    emp.getBonus(),
                    emp.getDeptno());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void modEmp(Emp emp) {

    }

    @Override
    public void delEmp(int empno) {

    }

    @Override
    public Emp findByEmpno(int empno) {
        Emp emp = null;
        try{
            emp = qr.query("select * from emp where empno = ?", new BeanHandler<>(Emp.class), empno);
        }catch (Exception e){
            e.printStackTrace();
        }
        return emp;
    }

    @Override
    public List<Emp> findAll() {
        List<Emp> emps = null;
        try {
            emps = qr.query("select * from emp", new BeanListHandler<>(Emp.class));
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return emps;
    }
}

测试:

package com.jdbc.empmanager.test;

import com.jdbc.empmanager.dao.EmpDao;
import com.jdbc.empmanager.dao.impl.EmpDaoImplOther;
import com.jdbc.empmanager.entity.Emp;
import org.junit.Test;

import java.sql.Date;
import java.sql.SQLException;

public class TestDBUtilsOther {
    @Test
    public void testFindAll() throws SQLException {
        EmpDao dao = new EmpDaoImplOther();
        dao.findAll().forEach(System.out::println);
    }

    @Test
    public void testFindByEMpno() throws SQLException {
        EmpDao dao = new EmpDaoImplOther();
        Emp emp = dao.findByEmpno(7369);
        System.out.println(emp);
    }

    @Test
    public void testAdd(){
        EmpDao dao = new EmpDaoImplOther();
        dao.addEmp(new Emp(10005,"lily","clerk",
                7369, Date.valueOf("2020-10-10"),1000.0,10.0,10));
    }
}


http://www.kler.cn/a/590162.html

相关文章:

  • EagleTrader为何重申重要数据前后2分钟禁止交易?
  • 【CXX】6.10 *mut T,*const T原始指针
  • 搭建刷题专业版小程序系统
  • c++ 中的可变参数模板与折叠表达式
  • 从0到1,带你开启TypeScript的奇妙之旅
  • 《图解设计模式》 学习笔记
  • 自动注入@resource和@autowired的区别
  • Bridge Constructor Medieval for Mac 桥梁构造师:中世纪解谜建桥游戏 支持M、Intel芯片
  • uniapp+Vue3 开发小程序功能(下载文件)
  • 爬楼梯(js实现,LeetCode:70)
  • 每天五分钟深度学习PyTorch:循环神经网络RNN的计算以及维度信息
  • css的显示模式
  • Redis----大key、热key解决方案、脑裂问题
  • Matlab 舰载机自动着舰控制系统研究
  • SpringMVC(四)Restful软件架构风格
  • 【从零开始学习计算机科学】算法分析(三)动态规划 与 贪心算法
  • STM32---FreeRTOS事件标志组
  • 数学建模:MATLAB循环神经网络
  • PostgreSQL教程(二)九大类型
  • 第27周JavaSpringboot git初识