JDBC(Java语言连接数据库)

了解JDBC

JDBC全称是Java DataBase Connectivity,寓意是Java语言连接数据库

JDBC的本质
JDBC是SUN公司制定的一套接口
接口有调用者和实现者
面向接口调用,面向接口写实现类,都属于面向接口编程,目的是松耦合,降低程序的耦合度,提高程序的扩展力,典型的是多态机制,面向了抽象编程

JDBC过程
首先SUN公司定义了一套JDBC接口,然后Java程序员面向JDBC接口写代码,不同的数据库管理系统有不同的JDBC接口的实现类,比如MySQL,Oracle等编写的JDBC接口实现类都不同,这些厂家写好的实现类被称为MySQL驱动,Oracle驱动等.所谓的驱动都是以jar包形式存在,jar包中有很多.class文件,这些.class文件都是对JDBC接口的实现.驱动不是SUN公司的,而是各大数据库厂家提供的,下载驱动jar包需要去对应数据库厂家的官网下载

idea导入mysql驱动

MySQL官网下载mysql驱动jar包
官网下载地址==>由此去!
选择Connector/J,选择platform…,选择ZIP格式下载即可!

JDBC开发前的准备工作,先从官网下载jar包,然后配置到classpath当中,(.;jar包路径)注意分号必须是英文的,.;代表当前路径下找字节码文件也行,这是针对用编辑器开发才这么配置,如果是idea,直接导入jar包即可.

idea导入MySQL驱动
首先项目的模块上右键选择Open Module Settings,再点Libraries,点加号导入mysql驱动的jar包即可!

JDBC编程6步(必背)

第一步,注册驱动,作用是告诉Java程序我们连接的是哪个品牌的数据库
第二步,获取连接,表示JVM的进程和数据库进程之间的通道打开了,重量级的,使用后一定要关闭
第三步,获取数据库操作对象,专门执行sql语句的对象
一般使用预编译的数据库操作对象,所以执行SQL一般也会在第三步执行
第四步,执行SQL
第五步,处理查询结果集,只有第四步是查询语句,才会执行第五步
第六步,释放资源,使用完java和数据库属于进程的通信,开启后一定要关闭

解决SQL注入问题

SQL注入的根本原因:用户输入的信息含有SQL语句关键字,并且这些关键字参与sql语句的编译过程,导致SQL的原来的意思被扭曲,从而造成了SQL注入

SQL注入严重会改变数据库的信息,有安全隐患.

解决办法
只要用户提供的信息不参与SQL语句的编译过程,问题就解决了.
想要用户信息不参与SQL语句的编译,必须使用java.sql.PreparedStatement
PreparedStatement接口继承了java.sql.statement,属于预编译的数据库操作对象
PreparedStatement的原理是,预先对SQL语句的框架进行编译,然后给SQL语句传值

解决SQL注入的关键:就算用户提供的信息中含有关键字,但是这些关键字并没有参与编译,不起作用

Statement和PreparedStatement

Statement存在SQL注入问题,每次SQL语句的值都可能不一样,是编译一次执行一次
PreparedStatement解决了SQL注入问题,只需要编译一次SQL语句的框子,就能执行N次,效率更高,并且会在编译阶段做类型的安全检查,99%的几率都是使用的PreparedStatement

在某些特殊的场合下,需要使用SQL注入
业务方面要求必须支持SQL注入的时候
要求SQL语句拼接的时候

注意PreparedStatement的SQL框子的问号只能是值,不能是SQL关键字,否则发生错误

测试statement代码

package io;

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

/**
 * 用户输入desc或者asc,测试statement的使用场景
 */
public class CodeTest {
    public static void main(String[] args) {
        statementTest();
    }

    private static void statementTest() {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入desc或者asc,desc表示降序排列,asc表示升序:");
        String input = scanner.nextLine();

        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            //注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //获取连接
            connection = DriverManager.getConnection
                    ("jdbc:mysql://localhost:3306/mydatabase", "root", "981122");
            /*
            //获取预编译的数据库操作对象
            //String sql = "select ename from emp order by ename ?";
            //preparedStatement = connection.prepareStatement(sql);
            //拼接值
            //preparedStatement.setString(1, input);//发生错误,?只能传入值,不能是SQL关键字,而用户输入的input就是SQL关键字
            */
            //获取数据库操作对象
            statement = connection.createStatement();
            //执行SQL
            String sql = "select ename from emp order by ename " + input;
            resultSet = statement.executeQuery(sql);
            //获取查询结果集
            while (resultSet.next()) {
                System.out.println(resultSet.getString("ename"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //释放资源,如果不为null
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

JDBC事务机制

JDBC的事务是自动提交的,只要执行任意一条DML语句就自动提交一次
在实际开发中,通常是多条MDL语句共同联合才能完成的,必须保证多条DML语句同时成功或失败

演示JDBC的事务自动提交

package io;

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

/*
 * 测试JDBC事务的自动提交
 * 加断点测试结果:一条DML语句也会执行成功,不符合实际开发需求.
 */
public class CodeTest {
    public static void main(String[] args) {
        statementTest();
    }

    private static void statementTest() {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        try {
            //1.注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2,获取连接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "root", "981122");
            //3.获取预编译的数据库操作对象
            String sql = "update t_user set loginName = ? where realName = ?";
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1,"wangwu");
            preparedStatement.setString(2,"王五");
            //4.执行SQL
            int i = preparedStatement.executeUpdate();
            System.out.println(i);
            //重新给占位符传值
            preparedStatement.setString(1,"lisi");
            preparedStatement.setString(2,"李四");
            i = preparedStatement.executeUpdate();
            System.out.println(i);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //释放资源,如果不是null
            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

模拟用户登陆作业

业务需求:模拟用户登陆功能的实现
描述:程序运行的时候提供一个输入的入口,可以让用户输入用户名和密码,用户输入后提交信息,java程序收集到用户信息后,java程序连接数据库验证用户信息是否合法,合法登陆成功,不合法登陆失败.
准备:在实际开发中,表的设计会使用专业的建模工具PowerDesigner,需要安装这个建模工具软件

package io;

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

/**
 * 完成用户登陆信息效验
 */
public class CodeTest {
    public static void main(String[] args) {
        //初始化一个界面,用户登陆信息
        Map<String, String> userLoginInfo = initUI();
        //验证用户名,密码
        boolean loginSuccess = login(userLoginInfo);
        //最后输出结果
        System.out.println(loginSuccess ? "登陆成功" : "登陆失败");
    }

    /**
     * 效验用户登陆信息
     *
     * @param userLoginInfo 用户登陆信息
     * @return true表示成功, false表示失败
     */
    private static boolean login(Map<String, String> userLoginInfo) {
        //设置标记
        boolean loginSuccess = false;
        //获取用户输入的信息
        String loginName = userLoginInfo.get("loginName");
        String loginPwd = userLoginInfo.get("loginPwd");
        //扩大变量的作用于范围
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        //JDBC代码
        try {
            //1.注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2.获取连接
            connection = DriverManager.getConnection(
                    "jdbc:mysql://localhost:3306/mydatabase", "root", "981122");
            //3.获取预编译的数据库操作对象
            //这是SQL语句的框子,?代表占位符,用来接收值,?不能用单引号括起来
            String sql = "select * from t_user where loginName = ? and loginPwd = ?";
            //程序执行到此处会发送SQL框子给DBMS,DBMS对SQL进行预编译
            preparedStatement = connection.prepareStatement(sql);
            //之后给占位符?传值(第一个?下表是1,第二个?是2,JDBC的下表从1开始)
            preparedStatement.setString(1, loginName);
            preparedStatement.setString(2, loginPwd);
            //4.执行SQL
            resultSet = preparedStatement.executeQuery();
            //5.获取查询结果集
            while (resultSet.next()) {
                loginSuccess = true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //如果不为null,关流
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            //如果不为null,关流
            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            //如果不为null,关流
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return loginSuccess;
    }

    /**
     * 初始化用户界面
     *
     * @return 用户输入的用户名和密码等登陆信息
     */
    private static Map<String, String> initUI() {
        //创建键盘扫描对象
        Scanner scanner = new Scanner(System.in);
        System.out.print("用户名:");
        //接收用户输入的字符串类型
        String loginName = scanner.nextLine();
        System.out.print("密码:");
        //接收用户输入的字符串类型
        String loginPwd = scanner.nextLine();
        //把用户输入的信息存入Map集合
        //创建map集合对象
        Map<String, String> userLoginInfo = new HashMap<>();
        //存入信息
        userLoginInfo.put("loginName", loginName);
        userLoginInfo.put("loginPwd", loginPwd);
        return userLoginInfo;
    }
}

账户转账事务作业

准备SQL脚本
//balance double(7,2)//7表示有效数为7,2表示2位小数

drop table if exists t_act;
create table t_act(
actno int,
balance double(7,2)
);
insert into t_act(actno,balance) values(111,20000);
insert into t_act(actno,balance) values(222,0);
commit;
select * from t_act;

重点:三行代码
//关闭事务自动提交
connection.setAutoCommit(false);

最后手动提交数据,事务结束
connection.commit();

catch (Exception e) {
//如果发生错误,回滚
catch{
if (connection != null){
try {
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
}

JDBC的事务转账作业

package io;

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

/*
 * JDBC事务
 */
public class CodeTest {
    public static void main(String[] args) {
        statementTest();
    }

    private static void statementTest() {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        try {
            //1.注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2.获取连接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "root", "981122");
            //关闭事务自动提交,开启事务
            connection.setAutoCommit(false);
            //获取预编译的数据库操作对象
            String sql = "update t_act set balance = ? where actno = ?";
            preparedStatement = connection.prepareStatement(sql);
            //拼接值
            preparedStatement.setDouble(1, 10000);
            preparedStatement.setInt(2, 111);
            //执行SQL
            int count = preparedStatement.executeUpdate();
            /*//如果在这发生错误,数据会丢失
            String s = null;
            s.toString();*/
            //解决数据丢失的办法是在获取连接之后,关闭事务自动提交功能,调用setAutoCommit
            //拼接值
            preparedStatement.setDouble(1, 10000);
            preparedStatement.setInt(2, 222);
            //执行SQL
            count += preparedStatement.executeUpdate();
            System.out.println(count == 2 ? "转账成功" : "转账失败");
            //程序能执行到这,说明程序没有错误,手动提交数据,事务结束
            connection.commit();
        } catch (Exception e) {
            //如果发生错误,回滚
            if (connection != null){
                try {
                    connection.rollback();
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            e.printStackTrace();
        } finally {
            //释放资源,如果不为null
            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            //释放资源,如果不为null
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

封装JDBC工具类并测试

封装代码

package io;

import java.sql.*;

/*
 * JDBC工具类,简化JDBC编程
 * 工具类的构造方法应该是私有化的,因为工具类的方法都是静态方法,不需要对象,直接采用类名.调用
 */
public class JdbcUtil {
    //私有化构造方法
    private JdbcUtil() {
    }

    /**
     * JDBC:注册驱动
     * 静态代码块在类加载时执行,并且只执行一次
     */
    static {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * JDBC:获取连接
     * @return 连接对象
     */
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection
                ("jdbc:mysql://localhost:3306/mydatabase", "root", "981122");
    }

    /**
     * JDBC:释放资源
     * @param connection 连接对象
     * @param statement 数据库操作对象
     * @param resultSet 查询结果集
     */
    public static void close(Connection connection, Statement statement, ResultSet resultSet){
        //如果不为null
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

做模糊查询测试JDBC工具类

package io;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

/*
 * 测试JDBC工具,写模糊查询
 */
public class CodeTest {
    public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            //注册驱动和获取连接
            connection = JdbcUtil.getConnection();
            //获取预编译的数据库操作对象
            String sql = "select ename from emp where ename like ?";
            preparedStatement = connection.prepareStatement(sql);
            //拼接值
            preparedStatement.setString(1, "_A%");
            //执行SQL
            resultSet = preparedStatement.executeQuery();
            while (resultSet.next()) {
                System.out.println(resultSet.getString("ename"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

悲观锁和乐观锁

悲观锁(又叫行级锁:select后面添加for update)
事务必须排队执行,数据锁住了,不允许并发.

乐观锁
支持并发,事务也不需要排队,只不过需要一个版本号

事务1读取到的版本号是1.1
事务读取到的版本号也是1.1
事务1先修改了,查看版本号是1.1,于是提交修改的数据,将版本号改为1.2
事务2后修改了,看版本号是1.2,跟原来的1.1不一样,于是回滚

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值