jdbc笔记(I)

jdbc

一、jdbc的简介:

​ Sun公司参考了ODBC方案,制定了一组专门为java语言连接数据库的通用接口JDBC。方便了java开发人员,

开发人员不需要考虑特定的数据库的DBMS。JDBC不直接依赖于DBMS,而是通过驱动程序将sql语句转发给

DBMS,由DBMS进行解析并执行,处理结果返回。

jdbc的原理
在这里插入图片描述

jdbc中常用的接口和方法

名称类型作用方法(主要使用)
DriverManager方法连接数据库,返回Connection对象getConnection(String url,String user,String password)
Connection接口获取Statement对象createStatement()
Statement接口发送SQL语句execute(String sql) :通常用于DDL executeUpdate(String sql):通常用于DML executeQuery(String sql):通常用于DQL
ResultSet接口封装了DQL的返回值geXXX(int index) XXX表示需要的属性类型如:int,string getXXX(String columnLabel)

二、jdbc的入门案例:

案例一、查询操作

public class Test1 {
    public static void main(String[] args) {
        Connection conn = null;
        Statement stat = null;
        ResultSet rs = null;
        try {
            // 一、利用反射加载驱动类
            Class.forName("com.mysql.jdbc.Driver");
            // 二、获取;连接对象
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/abdc","root","123456");
            // 三、获取SQL执行的Statement对象
            stat = conn.createStatement();
            // 四、发送SQL语句,执行DQL语句,调用execueQuery方法
            rs = stat.executeQuery("select * from emp");
            while(rs.next()) {
                // 获取查询的数据
                int empno = rs.getInt(1);
                String ename = rs.getString("ename");
                // 打印查询到的语句
                System.out.println(empno + "\t" + ename);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            // 关闭连接
            rs.close();
            stat.close();
            conn.close();
        }
    }
}

案例二、删除操作

public class Test2 {
    public static void main(String[] args) {
        try {
            // 第一步:利用反射机制,加载mysql的驱动类型 com.mysql.jdbc.Driver到内存中
            Class.forName("com.mysql.jdbc.Driver");
            // 第二步:获取连接对象
            Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/abdc","root","123456");
            // 第三步:获取SQL语句的执行对象Statement
            Statement stat = conn.createStatement();
            // 第四步:发送SQL语句
            String sql = "delete from emp where empno = 1000";
            // 调用执行DML操作的方法 executeUpdate(String sql)
            int num = stat.executeUpdate(sql);
            // 打印一下几条数据受到影响
            System.out.println("受影响的条数为:" + num);
            stat.close();
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

批处理问题(一般只用于添加数据操作)

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

//批处理中使用的方法
addBatch(String sql):将sql语句存储到缓冲中
executeBatch():将缓冲中的sql语句全部冲刷到数据库中
/**
 * 批处理案例
 */
public void testBatch(){
        try {
            for (int i = 0; i < 100000; i++) {
                int num = (int) (Math.random() * 2);
                String gender = null;
                if (num == 0) {
                    gender = "f";
                } else {
                    gender = "m";
                }
                String sql = "insert into testbatch values ("+i+",'zs" + i + "','" + gender + "')";
				// 将sql语句存储到缓冲中
                stat.addBatch(sql);
                // 冲刷缓冲
                if(i%1000==0){
                    stat.executeBatch();
                }
            }
            // for循环结束后,缓冲里可能还有数据,再次冲刷
            stat.executeBatch();
        } catch (SQLException e) {
            e.printStackTrace();
        }

    }

三、jdbc的注入问题:

案例:APP登录案例

/**
 * 客户账户信息类
 * 为表bank_account设计一个实体类型,也就是表的字段与java类型的属性的映射关系
 */
public class Account {
    // 提供属性
    private int id;
    private String account_id;
    private double balance;
    private String realName;
    private String password;
    private String idcard;
    private Timestamp timestamp;
    private String gender;

    public Account() {
    }

    public Account(int id, String account_id, double balance, String realName, String password, String idcard, Timestamp timestamp, String gender) {
        this.id = id;
        this.account_id = account_id;
        this.balance = balance;
        this.realName = realName;
        this.password = password;
        this.idcard = idcard;
        this.timestamp = timestamp;
        this.gender = gender;
    }

    public int getId() {
        return id;
    }

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

    public String getAccount_id() {
        return account_id;
    }

    public void setAccount_id(String account_id) {
        this.account_id = account_id;
    }

    public double getBalance() {
        return balance;
    }

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

    public String getRealName() {
        return realName;
    }

    public void setRealName(String realName) {
        this.realName = realName;
    }

    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 Timestamp getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(Timestamp timestamp) {
        this.timestamp = timestamp;
    }

    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(account_id, account.account_id) &&
                Objects.equals(realName, account.realName) &&
                Objects.equals(password, account.password) &&
                Objects.equals(idcard, account.idcard) &&
                Objects.equals(timestamp, account.timestamp) &&
                Objects.equals(gender, account.gender);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, account_id, balance, realName, password, idcard, timestamp, gender);
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", account_id='" + account_id + '\'' +
                ", balance=" + balance +
                ", realName='" + realName + '\'' +
                ", password='" + password + '\'' +
                ", idcard='" + idcard + '\'' +
                ", timestamp=" + timestamp +
                ", gender='" + gender + '\'' +
                '}';
    }
}

登录方法:如果账号和密码相符合,就可以登录

/**
 * 模拟服务器接收客户端的用户账号和密码,然后使用jdbc将来查询表中是否有此用户名和密码对应的记录,
 * 如果有,就将返回的记录封装成一个Account类型的对象。
 */
public class AppServer {
    public Account checkLogin(String accountId, String password) {
        Connection conn = null;
        Statement stat = null;
        ResultSet rs = null;
        Account account = null;
        try {
            conn = DButil.getConnection();
            stat = conn.createStatement();
            String sql = "select * from bank_account where account_id='" + accountId + "' and user_pwd='" + password + "'";
            rs = stat.executeQuery(sql);
            if(rs.next()) {
                int id = rs.getInt("id");
                double balance = rs.getDouble(3);
                String realName = rs.getString(4);
                String idcard = rs.getString(6);
                Timestamp timestamp = rs.getTimestamp(7);
                String gender = rs.getString(8);
                account = new Account(id,accountId,balance,realName,password,idcard,timestamp,gender);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return account;
    }
}

测试:

/**
 * 模拟app登录的客户端
 */
public class AppClient {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入账号:");
        String account_id = sc.next();
        System.out.println("请输入密码:");
        // String password = sc.next();
        // 由此引出jdbc的注入问题,如果恶意修改查询语句结构,将会错误
        String password = "111' or '1'='1";
        // 创建一个服务器
        AppServer server = new AppServer();
        Account account = server.checkLogin(account_id,password);
        if(account!=null){
            System.out.println("输入正确");
        }else {
            System.out.println("输入错误,重新输入");
        }
    }
}

安全隐患

​ statement对象发送的语句结构可以被更改,可以添加一下其他条件,导致输入语句恒成立,这种问题就是SQL注入问题。有很大的安全隐患。

PreparedStatement类

​ PreparedStatement类型是Statement类型的子类型。

​ 此类型可以确定SQL语句的结构,无法通过其它方式来增减条件。

​ 此类型还通过占位符 "?"来提前占位,并确定语句的结构。

​ 然后在提供相应的赋值方式:

​ ps.setInt(int index,int value)

​ ps.setString(int index,String value)

​ ps.setDouble(int index,double value)

​ ps.setDate(int index,Date value)

​ index表示sql语句中的占位符 ? 的索引。从1开始

​ value:占位符所对应的要赋予的值

/**
 * 修改登录案例的服务端代码,将statment替换成子类型PreparedStatement
 */
public class AppServer1 {
    public Account checkLogin(String accountId, String password) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        Account account =null;
        // 获取连接
        conn = DButil.getConnection();
        try {
            String sql = "select * from bank_account where account_id=? and user_pwd=?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,accountId);
            ps.setString(2,password);
            rs = ps.executeQuery();
            if(rs.next()){
                int id = rs.getInt(1);
                double balance = rs.getDouble(3);
                String realName = rs.getString(4);
                String idcard = rs.getString(6);
                Timestamp timestamp = rs.getTimestamp(7);
                String gender = rs.getString(8);
                account = new Account(id,accountId,balance,realName,password,idcard,timestamp,gender);
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DButil.closeConnection(conn,ps,rs);
        }
        return account;
    }
}

四、jdbc中的事务:

4.1 银行转账案例:

1.需求:一个账号fromAccount向另一个账号toAccount转入money元钱

2.分析:-检查两个账号是否存在,不存在的话,结束转账行为

​ -检查转出账号的里金额是否充足,不充足,结束转账行为,充足的话,进行扣款money元

​ -转入账号进行增加money元

当转出账号已经扣钱后,发生异常,转入账号没有增加钱,会导致转账前后总金额不一致。因此我们应该确定新的规范,如果执行成功,那么钱就会转过去;如果失败,最后还是转账前的情况。对此我们应该把这个过程看成一个事务,如果成功,那么事务执行结束,如果失败,那么回滚到最初的情况。

4.2 事务的特征(ACID)

​ 1)原子性(A): 事务要么成功,要么失败,不可分割。

​ 2)一致性©: 事务开始前和结束后,数据必须保证一致。

​ 3)隔离性(I): 多个用户操作一张表时,每个用户一个事务,并且每个事务之间互不干扰。

​ 4)持久性(D): 一个事务被提交后,必须保证他可以持久存在。

4.3 事务的使用方法:(是对Connection接口的)
//此方法可以取消事务的自动提交功能,值为false。true可以提交事务。 
Connection.setAutoCommit(boolean flag)  
//进行事务提交。 
Connection.commit()
//进行事务回滚。
Connection.rollback()					
4.4 多事务遇到的问题
脏读不可重复读幻读
事务A读取了事务B刚刚更新的数据,但是事务B回滚了,这样就导致事务A读取的为脏数据。事务A读取同一条记录两次,但是在两次之间事务B对该条记录进行了修改并提交,导致事务A两次读取的数据不一致。事务A在修改全表的数据,在未提交时,事务B向表中插入或删除数据,这样导致事务A读取的数据与需要修改的数据不一致,就和幻觉一样
对于一个数据对于一个数据对于一张表
事务A读取事务B未提交的。事务A读取事务B开始前和结束后的。事务A读取事务B开始前和结束后的。
4.5 事务的隔离机制

​ 1)未提交读:就是不做隔离控制,可以读到“脏数据”,可能发生不可重复读,也可能出现幻读。

​ 2)提交读:提交就是不允许读取事务没有提交的数据。显然这种级别可以避免了脏读问题。但是可能发生不可重复读,幻读。这个隔离级别是大多数数据库(除了mysql)的默认隔离级别。

​ 3)可重复读:为了避免提交读级别不可重复读的问题,在事务中对符号条件的记录上“排他锁”,这样其他事务不能对该事物操作的数据进行修改,可以避免不重复读的问题产生。由于只对操作数据进行上锁的操作,所有当其他事务插入或删除数据时,会出现幻读的问题,此种隔离级别为mysql默认的隔离级别。

​ 4)序列化:在事务中对表上锁,这样在事务结束前,其他事务都不能够对表数据进行操作(包括新增,删除和修改),不可重复读和幻读,是最安全的隔离级别。但是由于该操作是堵塞的,因此会严重影响性能。

/**
 *  写一个银行转账客户端,说明事务的概念
 */
public class TransferTest {
    public static void main(String[] args) {
        String fromAccount = "6225113088436225";
        String toAccount = "6225113088436226";
        double money = 10000;
        String password = "zgl123456";
        boolean success = oneToOne(fromAccount,toAccount,money,password);
        if (success){
            System.out.println("成功");
        }else {
            System.out.println("失败");
        }
    }

    /**
     * 定义一个转账方法
     * @param fromAccount   转出账号
     * @param toAccount 转入账号
     * @param money 转账金额
     * @param password 转出账号密码
     * @return  转账是否成功
     */
    public static boolean oneToOne(String fromAccount,String toAccount,double money,String password){
        if (fromAccount==null||fromAccount.length()==0||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();
            // 在此处,关闭事务的自动提交,开启手动提交
            // true表示自动提交。
            conn.setAutoCommit(false);
            // 判断转出账号是否存在
            String sql = "select * from bank_account where account_id = ?";
            ps = conn.prepareStatement(sql);
            ps.setString(1,fromAccount);
            rs = ps.executeQuery();
            if (!rs.next()){
                System.out.println("转出账号不存在");
                return false;
            }
            // 账号存在,那么就保留一下账号的余额
            double balance = rs.getDouble("account_balance");
            // 判断余额是否充足,支持转账
            if (balance < money) {
                System.out.println("转出账号余额不足");
                return false;
            }
            // 判断转入账号是否存在
            ps.setString(1,toAccount);
            rs = ps.executeQuery();
            if(!rs.next()){
                System.out.println("转入账号不存在");
                return false;
            }
            // 到此,进行扣款操作
            String reduce = "update bank_account set account_balance = account_balance-? where account_id = ? and user_pwd = ?";
            ps = conn.prepareStatement(reduce);
            ps.setDouble(1,money);
            ps.setString(2,fromAccount);
            ps.setString(3,password);
            ps.executeUpdate();

            // 模拟一个异常操作
            int[] arr = {1,2};
            System.out.println(arr[3]);
            // 进行存款操作
            String add = "update bank_account set account_balance = account_balance+? where account_id = ?";
            ps = conn.prepareStatement(add);
            ps.setDouble(1,money);
            ps.setString(2,toAccount);
            ps.executeUpdate();
            // 关闭事务
            //conn.setAutoCommit(true); 可以开启自动提交事务
            conn.commit();
            // 到此存款成功
            return true;


        } catch (SQLException e) {
            e.printStackTrace();
            try {
                conn.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }finally {
            DButil.closeConnection(conn,ps,rs);
        }
        return false;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值