JDBC-数据库

JDBC(Java DataBases Connectivity):Java语言连接数据库

JDBC的本质:一套接口

JDBC简介:
JDBC时SUN公司指定的一套接口(interface)
java.sql.*;(这个包下有很多接口)

为什么SUN要制定一套JDBC接口?
因为每一个数据库的底层实现原理都不一样。Oracle数据库、MySQL数据库、微软SqlService数据库都有自己独特的实现原理

JDBC.png

SUN公司提供JDBC接口,各大数据库厂商负责编写JDBC接口的实现类。我们只需要下载驱动就行。
驱动:所有的数据库驱动都是以jar包的形式存在,jar包当中有很多.class文件。这些.class文件就是对JDBC接口的实现。驱动不是SUN公司提供的,是各大数据库厂家负责提供,下载驱动jar包需要去数据库官网下载。

为什么要面向接口编程?
解耦合:降低程序的耦合度,提高程序的扩展力

JDBC编程6步概述(重点):


第一步:注册驱动:告诉Java程序即将连接的是哪种数据库
第二步:获取连接:表示JVM的进程和数据库进程之间的通道打开了。这属于进程间的通信(重量级),使用完一定要关闭
第三步:获取数据库操作对象:专门执行sql语句的对象
第四步:执行sql语句:
executeQuery(select)专门执行DQL语句、executeUpdate(insert、delete、update)专门执行DML语句
第五步:处理查询结果集:只有当第四步执行的是select语句时,才能有第五步
第六步:释放资源:使用完资源之后一定要关闭资源


第一步:注册驱动

//注册驱动的第一种写法:
Driver driver  = new com.mysql.cj.jdbc.Driver();
DriverManager.registerDriver(driver);

//注册驱动的第二种写法:
class.forName("com.mysql.cj.jdbc.Driver");
  • 第二种写法常用!因为参数是字符串,可以写到配置文件中

第二步:获取连接:

url:统一资源定位符(网络中某个资源的绝对路径)
url包括:
协议——jdbc:mysql://
ip——127.0.0.1和localhost都是本机ip地址
端口号——3306(MySQL数据库的端口号)
资源名(数据库名)——myjdb

oracle的url:
jdbc:oracle:thin:@localhost:1521:数据库名

url-IP地址

url-端口号

url-资源名
String url = "jdbc:mysql://127.0.0.1:3306/myjdbc";
String user="root";
String password="abc1234.";//MySQL登录密码

Connection conn=DriverManager.getConnection(url,user,password);
System.out.println("数据库连接对象="+conn);


第三步:获取数据库操作对象

Statement stmt = conn.createStatement();

Statement专门执行sql语句


第四步:执行sql语句

JDBC的sql语句不需要写分号(;)结尾

String sql="insert into school(name,age)  values('张三',20)";
int count=stmt.executeUpdate(sql);
System.out.println(count==1?"保存成功":"保存失败");

executeUpdate专门执行DML语句的(insert、delete、update)
返回值是单个ResultSet对象

executeQuery专门执行DQL语句(select)
返回值是“影响数据库中的记录条数”


第五步:处理查询结果集

查询结果集next():将光标从当前位置向前移一行,并返回boolean类型。

取数据getString():不管数据库中数据类型是什么,都以String的形式取出。
除了可以以String类型取出之外,还可以以特定的类型取出(得与底层数据类型一致)。如int类型getInt()、double类型的getDouble()。

JDBC中所有下标从1开始

executeQuery(select)专门执行DQL语句
executeUpdate(insert、delete、update)专门执行DML语句

定义与查询结果有关的变量
执行DQL语句
处理查询结果集-下标写法

处理查询结果集-列名写法

注意:列名不是表中的列名称,而是查询结果集的列名称


第六步:释放资源

为了保证资源一定释放,在finally语句块中关闭资源并且要遵循从小到大(先开后关)依次关闭分别对其try...catch。
在try语句中定义的对象无法关闭,需要把定义放到try语句外。

            try{
                if(stmt!=null){
                    stmt.close();
                    System.out.println("关闭stmt");
                }
            }catch (Exception e){
                e.printStackTrace();
            }
            try{
                if(conn!=null){
                    conn.close();
                    System.out.println("关闭conn");
                }
            }catch (Exception e){
                e.printStackTrace();
            }

全过程:


将连接数据库的所有信息配置到配置文件中

实际开发中不建议把连接数据库的信息写死到Java程序中

配置文件存放在src目录下,则不用加路径

配置文件中定义变量
使用资源绑定器绑定属性配置文件(properties)

项目实战-用户登录功能

实现功能:
1.模拟用户登录功能的实现
2.业务描述:
程序运行的时候,提供一个输入的入口,可以让用户输入用户名和密码。用户输入用户名和密码之后,提交信息,Java收集并验证用户名和密码是否合法。
合法:显示登陆成功
不合法:显示登陆失败
3.数据的准备:
在实际开发中,表的设计会使用专业的建模工具如:Power Designer来进行数据库表的设计。参见user_login.sql脚本

1.用户输入界面

    private static Map<String, String> initUI() {
        //获取用户名
        Scanner s =new Scanner(System.in);
        System.out.print("用户名:");
        String loginName=s.nextLine();
        System.out.print("密码:");
        String loginPwd=s.nextLine();
        //创建一个 Map<String,String>类型的哈希表,名为userLoginInfo
        Map<String,String> userLoginInfo = new HashMap<>();
        //利用put方法把用户输入保存在哈希表中
        userLoginInfo.put("loginName",loginName);
        userLoginInfo.put("loginPwd",loginPwd);
        //返回用户输入,用以下一步验证
        return userLoginInfo;
    }
  • 知识点小结:
    1.Scanner类可以帮助我们接收从键盘输入的数据
    2.Scanner类next()与nextLine()的区别
    1)next()
    ①一定要读取到有效字符后才可以结束输入。
    ②对输入有效字符之前遇到的空白,next() 方法会自动将其去掉。
    ③只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符。
    ④next() 不能得到带有空格的字符串。

    2)nextLine()
    ①以Enter为结束符,即nextLine()方法返回的是输入回车之前的所有字符。
    ②可以获得空白。

    3.将对象保存在Map<String,Sring>容器中
    Map是存储键和值这样的双列数据集合,但存储的数据是没有顺序的,其键不能重复,但其值是可以重复的,可以通过每一个键找到每一个对应的值
    Map集合的特点:
    1)Map集合一次存储两个对象,一个键对象,一个值对象
    2)键对象在集合中是唯一的,可以通过键来查找值
    HashMap特点:
    1)使[哈希算法对键去重复,效率高,但无序
    2)HashMap是Map接口的主要实现类

2.验证用户输入

    private static boolean login(Map<String, String> userLoginInfo) {
        //使用资源绑定器绑定属性配置文件
        ResourceBundle bundle = ResourceBundle.getBundle("resource");
        //绑定后传值
        String driver = bundle.getString("driver");
        String url = bundle.getString("url");
        String user = bundle.getString("user");
        String password = bundle.getString("password");

        Connection conn = null;
        Statement stmt=null;
        ResultSet rs=null;

        //保存登录结果,默认为false
        boolean loginJieGuo = false;

        try{
            //1.注册驱动
            Class.forName(driver);
            //2.获取连接
            conn=DriverManager.getConnection(url,user,password);
            //3.获取数据库操作对象
            stmt=conn.createStatement();
            //4.执行sql语句
            String sql="select * from t_user where loginName='"+userLoginInfo.get("loginName")+"' " +
                    "&& loginPwd='"+userLoginInfo.get("loginPwd")+"'";
            rs=stmt.executeQuery(sql);
            //5.处理查询结果集
            if (rs.next()){
                //登录成功,将登录结果改为true
                loginJieGuo=true;
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //6.关闭资源
            if (rs != null) {
                try{
                    rs.close();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            if (stmt != null) {
                try{
                    stmt.close();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try{
                    conn.close();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
        //执行完后返回登陆结果
        return loginJieGuo;
    }
//resource.properties配置文件
driver =com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/myjdbc
user=root
password=abc1234.

3.主函数调用方法

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

以上程序存在SQL注入问题

用户名:fdsa
密码:fdsa' or '1'='1
登陆成功
这种现象被成为SQL注入。(黑客经常使用)

导致sql注入的根本原因是?
用户输入的信息中含有sql语句的关键字,并且这些关键字参与sql语句的编译过程,导致sql语句的愿意被扭曲,进而达到sql注入。

解决sql注入问题:
只要用户提供的信息不参与sql语句的编译过程,问题就解决了。即使用户提供的信息中含有sql语句的关键字,但没有参与编译,不起作用。

要想用户信息不参与sql语句的编译,那么必须使用
java.sql.PreparedStatement

PreparedStatement接口继承了java.sql.Statement
PreparedStatement是属于预编译的数据库操作对象。
PreparedStatement的原理 是:预先对sql语句的框架进行编译,然后在给sql语句传值。

  • 1.将原第三步:获取数据库操作对象中
    Statement stmt=null;修改为 PreparedStatement stmt=null;


    解决sql注入1
  • 2.修改原sql语句

String sql="select * from t_user where loginName='"+loginName+"'&& loginPwd='"+loginPwd+"'";
改为
String sql="select * from t_user where loginName= ? && loginPwd= ? ";

?为填充符,一个?将来接受一个“值”。且占位符不能用单引号括起来

解决sql注入2

  • 3.将原第四步:执行sql语句放在第三步:获取预编译的数据库操作对象中进行


    解决sql注入3
  • 4.在第四步:执行sql前,给占位符传值


    解决sql注入4

数据是什么类型的,就是set什么类型的方法。如int,setInt()。
setXxxx(int parameterIndex,Xxxx x )
parameterIndex:占位符的下标
x:值
JDBC中下标从1开始

解决sql注入5


Statement与prepareStatement对比:

1.Statement存在sql注入现象,prepareStatement解决了SQL注入问题
2.prepareStatement效率较高一些
MySQL数据库中,第一条指令编、译执行后,如果第二条指令与第一条完全一致,则不会再编译,直接执行。
因为用户名与密码每次都不一样,故Statement每次都要先编译再执行。


因为整个sql语句是固定的,故prepareStatement只有第一次需要编译,而后可以直接执行。(?也不会变,因为传值时间在编译之后)

3.prepareStatement会在编译阶段做类型的安全检查

什么情况下必须使用Statement呢?

业务方面要求必须支持SQL注入时(即业务要求是需要进行SQL语句拼接的)
如:select * from t_user order by xxx desc;降序排列,此语句为select * from t_user order by xxx 拼接desc。
若:select * from t_user order by xxx ?再调用setString(1,desc)进行赋值。
结果为:select * from t_user order by xxx ‘desc’;这不符合sql语句的规范。
故当业务必须支持sql注入(sql拼接)时,必须使用Statement。如果只是给sql语句传值,使用prepareStatement。


JDBC的事务自动提交机制

事务自动提交

1.将自动提交机制改为手动提交
conn.setAutoCommit(false);
应在预编译前开启事务
2.测试是否手动提交事务

String s=null;
s.toString();

3.事务结束,手动提交数据
conn.conmmit();
4.手动设置回滚

//如果有异常,则在catch里设置回归
try{
......
}catch(Exception e){
  if(conn!=null){
    try{
    conn.rollback();//设置回滚
}catch(SQLException e1){
    e1.printStackTrace();
}
}
}

JDBC工具类的封装及使用

    /*
    *   工具类中的构造方法都是私有的,
    *   因为工具类当中的方法都是静态的,
    *   不需要new对象,直接采用类名调用。
    * */
    private DBuntil(){}
    /*
    *   注册驱动只需要执行一次
    *   静态代码块在类加载时执行,且只执行一次
    * */
    static {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    /*
    *   获取数据库连接对象
    *   返回连接对象
    * */
    public  static Connection getConnection() throws SQLException{
        return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/myjdbc","root","abc1234.");
    }
    /*
    *    执行DQL语句
    *    conn连接对象
    *   ps数据库操作对象
    *   rs结果集
    * */
    public static void close(Connection conn, Statement ps, ResultSet rs){
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    /*
     *    执行DML语句
     *    conn连接对象
     *   ps数据库操作对象
     * */
    public static void close(Connection conn, Statement ps){
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
JDBC工具类的使用

使用JDBC工具类进行模糊查询

锁的概念

  • 行级锁(for update)又被称为悲观锁
    select * from school where age>20 for update;
    在select语句后+for update表示,当前事务没有结束前,对其他事务来说,对满足条件“age>20”的数据,所在行的字段都无法修改。

    行级锁

  • 乐观锁
    多线程并发都可以对数据进行修改,不需要盘对,但需要一个版本号。


    乐观锁示例

笔记来源:B站动力节点 jdbc学习视频

视频链接:https://www.bilibili.com/video/BV1Bt41137iB?spm_id_from=333.788.b_636f6d6d656e74.11

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容