【JavaWeb·2】JDBC详解

JDBC

环境准备

1、创建project(项目)

  • 先根据自身需求创建一个project路径
  • 左上角File->project->Empty/project…

2、创建module(模块)

File->new->module->对话框中Java->next…

3、需要在当前module下引入需要的 jar 包

  • 下载jar包
  • 在当前的module下new一个Directory(目录),取名为lib
  • 需要将jar包拷贝进lib文件夹中
  • jar包上右键选择add as Library 点击ok

4、在当前module->src 创建package

5、创建类

引言

1、JDBC:Java DataBase Connectivity,Java数据库连接技术

注意:利用Java语言来实现数据库的客户端

2、JDBC连接数据库的方式

(1) 直接连接方式

(2) 利用连接池技术进行连接

3、JDBC中提供访问数据库的通用接口和实现类,可以访问不同的数据库(mysql、oracle、DB2等)

4、JDBC中包含两部分

(1) 接口:由sun提供,存储在jdk中,只要安装过jdk就可以使用。

(2) 实现类:由各个数据库厂商提供,将对应的实现类打成jar包放在各自的官网上,直接下载即可。

jar包的版本需要和安装的数据库服务器版本一致。

5、JDBC开发思路

数据库客户端连接数据库的步骤

  • 连接数据库:输入用户名、密码
  • 准备写SQL的页面(创建一个新的查询页面)
  • 写sql,并运行sql (将客户端的指令发送给服务端)
  • 数据库的服务端将结果返回给客户端
  • 关闭窗口,释放连接,同时将数据结果清除,将所有数据库资源释放

JDBC:利用Java语言操作数据库的技术,模拟一个客户端

  • 获取数据库的连接,用户名、密码,Connection对象,只有获取到Connection对象,数据库才连接成功。
  • 准备发送sql的工具,获取PrepareStatement,准备sql
  • 发送sql,利用发送sql工具中的工具方法完成
  • 处理结果集(针对select):ResultSet
  • 释放资源

JDBC的第一个程序

分为两个准备和六个步骤

1、两个准备

(1) 第一个准备:将mysql对应的JDBC依赖jar包引入当前module

(2) 第二个准备:利用navicat将库表准备好

  • 在当前的连接中创建一个新的数据库,名称为bank,注意编码格式为utf-8
  • 当前bank库中创建表
    • t_account

在这里插入图片描述

2、六个步骤

  • 加载驱动类

    Class.forName(“com.mysql.jdbc.Driver”);

  • 获取连接

    String url = “jdbc:mysql://localhost:3306/bank?characterEncoding=utf-8”;

    Connection conn = DriverManager.getConnection(url,“root”,“123456”);

    参数说明:

    url = “协议:子协议://ip地址:端口号/数据库名?characterEncoding=字符集编解码格式”;

    第二个参数:连接数据库的用户名

    第三个参数:用户名对应的密码

  • 准备sql,同时准备发送sql的工具

    String sql = “”;

    PreparedStatement pstm = conn.prepareStatement(sql);

  • 发送sql:

    int n = pstm.executeUpdate();

  • 返回值代表影响数据库行数

  • 只能发送增删改insert/delete/update

ResultSet rs = pstm.executeQuery();

  • 返回值代表查询到数据结果

  • 只能发送 select 类型的 sql

  • 处理结果集: select操作,处理 ResultSet 结果集

    ResultSet的常见方法

    • rs.next():判断结果集中是否有数据,有为true,没有为false,调用一次next()会让数据的指针移动一行数据,取数据时,指针指向的这行数据会被处理

    • 当 rs.next() 为true时,可以从结果集中获取对应数据:

      • Xxx 变量名 = rs.getXxx(“字段名”);

        数据库中类型Java中类型
        intint
        doubledouble
        varcharString
        charString

        int id = rs.getInt(“accId”);

        String name = rs.getString(“accname”);

        double balance = rs.getDouble(“balance”);

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c8cqbJh0-1655910124484)(C:\Users\72442\AppData\Roaming\Typora\typora-user-images\image-20220620110405785.png)]

    • Xxx 变量 = rs.getXxx(列的编号)

      注意:在指定列时,可以采用列名或是列编号。编号根据 select 后面指定的列顺序进行编号,自动的编号默认从左往右从1开始

      注意:如果select后面是*,则字段顺序默认为表中定义的顺序,编号是根据表中字段定义的顺序。

      注意:如果通过字段名获取对应字段数据时,字段有别名,需要根据别名进行获取。

  • 释放资源:先开启的后释放,rs pstm conn

    if(rs != null) rs.close();

    if(pstm != null) pstm.close();

    if(conn != null) conn.close();

JDBCAPI

DriverManager

DriverManager(驱动管理类)作用:

  • 注册驱动
  • 获取数据库连接

1、加载驱动

Class.forName("com.mysql.jdbc.Driver");

查看Driver源码

static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }

提示:MySQL5之后的驱动包,可以省略注册驱动的步骤,自动加载jar包中的META-INF/services/java.sql.Driver文件中的驱动类

2、获取连接

getConnection(String url, String user, String password);

参数:

1、 url:连接路径

语法:jdbc:mysql://ip地址:端口号/数据库名称?参数键值对1&参数键值对2...

细节:

  • 如果连接的是本机mysql服务器,并且mysql服务默认端口为3306,则url可以简写为

    jdbc:mysql:///数据库名?参数键值对1&参数键值对2...
    

2、user:用户名

3、password:密码

Connection

connection(数据库连接对象)作用:

  • 获取执行SQL的对象
  • 管理事务

1、获取执行SQL的对象

  • 普通执行SQL对象
Statement createStatement()
  • 预编译SQL的执行SQL对象:防止SQL注入
PreparedStatement prepareStatement(sql)

2、管理事务

开启事务:setAutoCommit(boolean autoCommit): true为自动提交事务;false为手动提交事务,即开启事务
提交事务:commit()
回滚事务:rollback()
try {
            //开启事务
            conn.setAutoCommit(false);
            //5、执行sql
            int count = stmt.executeUpdate(sql);    //返回受影响的行
            System.out.println(count);
            int in = 3/0;
            int count2 = stmt.executeUpdate(sql2);
            //6、处理结果
            System.out.println(count2);
            //提交事务
            conn.commit();
        } catch (Exception throwables) {
            //回滚事务
            conn.rollback();
            throwables.printStackTrace();
        }
使用此,可以让两条sql都更改不成功        

ResultSet

封装了DQL查询语句的结果

ResultSet stmt.executeQuery(sql):执行DQL语句,返回ResultSet对象

获取查询结果

boolean next():(1)将光标从当前位置向前移动一行(2) 判断当前行是否为有效行

返回值:

  • true:有效行,当前行有数据
  • false:无效行,当前行没有数据
xxx getXxx(参数):获取数据

xxx:数据类型,如int getInt(参数) String getString(参数)

参数:

  • int:列的编号,从1开始
  • String:列的名称
ResultSet使用步骤
1、游标向下移动一行,并判断该行是否有数据:next()
2、获取数据:getXxx(参数)

//循环判断游标是否是最后一行末尾
while(rs.next()){
//获取数据
rs.getXxx(参数);
}

PreparedStatement

  1. 获取PreparedStatement对象
// SQL语句中的参数值,使用?占位符替代
String sql = "select * from user where username = ? and pssword = ?";
// 通过Connection 对象获取,并传入对应的sql语句
PreparedStatement pstm = conn.prepareStatment(sql);
  1. 设置参数值
PreparedStatement对象:setXxx(参数1,参数2):给?赋值
Xxx:数据类型
参数:参数1:?的位置编号,从1开始
	 参数2:?的值

1、占位符:?,只能代表某一个数值,只能为数据占位,不能为字段占位。提高代码灵活度。

"select * form t_account where id=?"

2、为?绑定参数,一定在发送sql之前

  • 为每一个?设置一个编号,从1开始,从左往右依次为1、2、3.。。

  • 在发送sql之前,完成?占位符的数据绑定

    pstm.setXxx(?编号, 值);

    Xxx代表数据类型,取决于 ?对应的字段数据类型,对应关系:

    数据库中字段类型Java中数据类型
    intint
    doubledouble
    char/varcharString

    pstm.setInt(编号,int类型数值);

    pstm.setInt(编号,double类型数值);

    pstm.setInt(编号,String类型数值);

PreparedStatement 好处

  • 预编译SQL,性能更高
  • 防止SQL注入:将敏感字符进行转义

SQL注入

SQL注入是通过操作输入来修改实现定义好的SQL语句,用以达到执行代码对服务器进行攻击的方法。

SQL注入问题:

'or '1'= '1
password = ''or '1'= '1'

PreparedStatement会帮我们把字符转义,就解决了sql注入的问题

password = '\'or \'1\'= \'1\'

PreparedStatement预编译功能开启:useServerPrepStmts=true

配置MySQL执行日志(重启sql服务后生效)将下列部分添加到 my.in 目录下

log-output=FILE
general-log=1
general_log_file="D:\mysql.log"
slow-query-log=1
slow_query_log_file="D:\mysql_slow.log"
long_query_time=2

在这里插入图片描述
在这里插入图片描述

查看D盘中的日志文件,即可看到编译期已经进行转义,防止sql注入了。

当我们开启预编译

在这里插入图片描述

查看日志文件

在这里插入图片描述

可以看出获取pstm对象的时候,就已经预编译了,执行时直接执行,预编译只进行一次。

PreparedStatement原理总结

  • 在获取PreparedStatement对象时,将sql语句发送给mysql服务器进行检查,编译(这些步骤很耗时)
  • 执行时就不用进行这些步骤了,速度很快。
  • 如果sql模板一样,只需要进行一次检查、编译。

JDBC开发步骤

  1. 加载驱动类
  2. 获取连接
  3. 准备sql,同时准备发送sql的工具
  4. 绑定参数,再发送sql
  5. 处理结果集
  6. 释放资源

JDBCUtil工具类

第一个版本

  • 定义一个JDBCUtil类

  • 类中提供两个静态方法

    • 获取连接方法:1、加载驱动类 2、获取连接
    • 释放资源方法:释放资源
  • 第一个版本解决了代码冗余问题,将JDBC中六个步骤中重复的步骤进行提取

  • 存在问题:没有解决因数据库发生改变而带来的工具类的变化,不够灵活。

public class JDBCUtil {
    private JDBCUtil() {
    }
    //获取连接
    public static Connection getConnection() {
        try {
            //加载驱动类
            //获取连接
            Class.forName("com.mysql.jdbc.Driver");
            String url = "jdbc:mysql://localhost:3306/bank?characterEncoding=utf-8";
            Connection conn = DriverManager.getConnection(url, "root", "791345");
            return conn;
        } catch (Exception e) {
            e.printStackTrace();
            //讲出的异常转化成运行时异常通知调用者
            throw new RuntimeException("获取连接失败啦", e);
        }
    }

    public static void close(ResultSet rs, PreparedStatement pstm, Connection conn) {
        //释放资源
        try {
            if (rs != null) {
                rs.close();
            }
            if (pstm != null) {
                pstm.close();
            }
            if (conn != null) {
                conn.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
            throw new RuntimeException("关闭资源出错啦!", throwables);
        }
    }
}

第二个版本

1、解决:数据库不同 参数需要改变,提高灵活性、通用性

2、在当前module中创建 一个包

/com/baizhi/hjq/conf

创建一个配置文件db.properties,配置文件中内容
在这里插入图片描述

3、利用Properties,是Map的实现类,key和value默认为String类型。

load(输入流):自动将输入流指向文件中内容加载读取到properties中,自动以=做拆分,=左边为key,=右边为value,每一行对应一个key-value

注意:使用类加载流

InputStream in = JDBCUtil1.class.getResourceAsStream("/com/baizhi/hjq/conf/db.properties");
写路径时以/开头,以module的src下一级目录开始写,到properties文件所在的路径即可,中间都是以/隔开

getProperties(key):根据键获取对应的value。

public class JDBCUtil {
    private JDBCUtil() {
    }
    //获取连接
    public static Connection getConnection() {
        try {
            //加载驱动类
            //获取连接
            Class.forName("com.mysql.jdbc.Driver");
            String url = "jdbc:mysql://localhost:3306/bank?characterEncoding=utf-8";
            Connection conn = DriverManager.getConnection(url, "root", "791345");
            return conn;
        } catch (Exception e) {
            e.printStackTrace();
            //讲出的异常转化成运行时异常通知调用者
            throw new RuntimeException("获取连接失败啦", e);
        }
    }

    public static void close(ResultSet rs, PreparedStatement pstm, Connection conn) {
        //释放资源
        try {
            if (rs != null) {
                rs.close();
            }
            if (pstm != null) {
                pstm.close();
            }
            if (conn != null) {
                conn.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
            throw new RuntimeException("关闭资源出错啦!", throwables);
        }
    }
}

第三版本

1、解决效率问题

2、将读取配置文件的内容,定义在静态代码块中,在类加载时执行一次即可。

public class JDBCUtil1 {
    private static final Properties pro = new Properties();
    private JDBCUtil1() {
    }
    static{
        //将配置文件加载到Java程序中
        try {
            InputStream in = JDBCUtil1.class.getResourceAsStream("/com/baizhi/hjq/conf/db.properties");
            pro.load(in);
        } catch (IOException e) {
            e.printStackTrace();
            //讲出的异常转化成运行时异常通知调用者
            throw new RuntimeException("获取连接失败啦", e);
        }
    }
    //获取连接
    public static Connection getConnection() {
        try {

            String driver = pro.getProperty("mydriver");
            String url = pro.getProperty("url");
            String username = pro.getProperty("username");
            String password = pro.getProperty("password");
            //加载驱动类
            Class.forName(driver);
            //创建连接
            Connection conn = DriverManager.getConnection(url, username, password);
            return conn;
        } catch (Exception e) {
            e.printStackTrace();
            //讲出的异常转化成运行时异常通知调用者
            throw new RuntimeException("获取连接失败啦", e);
        }
    }

    public static void close(ResultSet rs, PreparedStatement pstm, Connection conn) {
        //释放资源
        try {
            if (rs != null) {
                rs.close();
            }
            if (pstm != null) {
                pstm.close();
            }
            if (conn != null) {
                conn.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
            throw new RuntimeException("关闭资源出错啦!", throwables);
        }
    }
}

开发步骤:

  • 1-2通过工具类获取连接
  • 3、准备sql,同时准备发送sql的工具
  • 绑定参数,同时发送sql
  • 处理结果集
  • 通过工具类JDBCUtil释放资源

Dao

1、Dao:Data Access Object(数据访问对象)

2、dao中需要封装对一张表的所有操作,例如:增删改查

3、Dao类设计思想:

  • 通常利用一个Dao类对应数据库表中的一张表

  • 表中不同 sql 操作,对应类中不同的功能方法。

    • 通常对表中增删改查操作需求,对应Dao类中的方法。

    • dao类中方法功能要单一。

    • 每一个方法的开发步骤:6步骤(jdbc开发步骤)。

4、Dao类命名要求

(1)数据库中一张表通常对应一个Dao类

(2)类名要求:表名+Dao 类名首字母大写,驼峰命名,去除_。

注意:Java命名里,不要有 _!!!!

(3)dao类存储的位置:com.baizhi.hjq.dao包下

(4)dao中的每个方法出现的sql必须在navicat中先进行测试

ORM

(1)ORM:object Relational Mapping 对象映射关系

Java中一个类对应数据库一张表(数据库中一行数据对应Java中一个对象)

(2)实体类:和表映射对应的类,称为实体类

(3)实体类的要求:

  • 类名取决于对应的表名,通常是将对应的表的首字母大写作为类名

    数据库中库表Java中实体类名
    studentsStudent
    t_accountAccount
  • 实体类存储在com.baizhi.hjq.entity包中

  • 实体类要求实现Serializable接口

  • 实体类中的属性取决于对应表的字段:个数、类型、名字

表中字段Java中属性
acc_id intInteger accId
accName charString accName
  • 属性私有化,提供公开的get/set方法
  • 提供两个构造方法:无参数和有参数的构造方法
  • 重写toString() 方便展示信息

6、dao中常见方法

class 表名+Dao{
	//删除
	public void deleteById(int id){}
	//根据id查询
	public Account selectOne(int id){}
	//查询所有
	public List<Account> queryAll(){}
}

业务层

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tc7qhPIn-1655910124493)(C:\Users\72442\AppData\Roaming\Typora\typora-user-images\image-20220622104038280.png)]

1、业务:代表根据用户需求完成一个对用功能,通常由一次或多次调用dao中方法完成。

2、业务类:专注于对用户的业务功能进行处理,一个业务对应业务类中的一个方法。

  • 业务类:望文生义,XxxService,例如AccountService、BankService
  • 业务类:存储在com.baizhi.hjq.service包中
  • 业务类中的每一个方法代表一个业务功能,方法名望文生义即可。
  • 完成业务功能时,需要控制事务:
    • 事务:是由一条sql或是多条sql组成的,所有sql都执行成功,则需要提交事务(commit)只要有一条sql执行失败,则回滚事务(rollback)
    • 事务由业务决定,一个业务对应一个事务,业务大小决定了事务中sql的组成。
    • 利用jdbc中的事务控制,保证事务原子性

3、JDBC中事务控制的API

  • 目前事务控制失败的原因:JDBC中默认自动提交事务,执行1条sql就提交一次事务

  • 控制事务常见的方法,位于Connection中:

    //获取链接
    Connection conn = JDBCUtil.getConnection();
    
    //将事务控制的方式设置成手动提交
    conn.setAutoCommit(false);
    
    //提交事务
    conn.commit();
    
    //事务的回滚
    conn.rollback();
    

业务中控制事务的模板

public 返回值 方法名(形参){
	Connection conn = null;
	try{
		//1、通过工具类JDBCUtil获取链接
		conn = JDBCUtil.getConnection();
		//2、设置事务提交方式为手动
		conn.setAutoCommit(false);
		//3、调用dao方法完成业务功能
		//4、提交事务
		conn.commit();
	}catch(Exception e){
		//4、回滚事务
		conn.rollback();
	}finally{
		//5、释放资源(只需要释放连接)
		JDBCUtil.close(null,null,conn);
	}
}

4、事务控制失败的原因

业务层service调用dao层的方法来实现对应的业务,业务层 dao 都是通过 JDBCUtil获取链接(service层和dao层获取的是不同链接),在service层中将提交方式设置成手动提交,但是dao层的连接的提交方式依然为自动提交service事务控制无法控制dao层。dao层执行一条sql,默认提交,导致service层事务回滚时失败。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rUAXVAYl-1655910124494)(C:\Users\72442\AppData\Roaming\Typora\typora-user-images\image-20220622143944217.png)]

5、解决思路:需要保证一个业务中的service和dao层获取同一个连接

6、ThreadLocal:利用ThreadLocal解决service和dao层连接不统一的问题

ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该

变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。

ThreadLocal 变量通常被private static修饰。

当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

ThreadLocal理解为:存储连接conn到容器

  • ThreadLocal常见的方法:

    ThreadLocal<Connection> tl = new ThreadLocal();
    
    Connection conn = tl.get();	//获取链接
    
    tl.set(conn);	//将连接存储在ThreadLocal
    
    tl.remove();	//清楚tl中的连接
    

7、JDBCUtil最终版本

public class JDBCUtil {
    private static final Properties pro = new Properties();
    private static final ThreadLocal<Connection> tl = new ThreadLocal();
    private JDBCUtil() {
    }
    static{
        //将配置文件加载到Java程序中
        try {
            InputStream in = JDBCUtil.class.getResourceAsStream("/com/baizhi/hjq/conf/db.properties");
            pro.load(in);
        } catch (IOException e) {
            e.printStackTrace();
            //讲出的异常转化成运行时异常通知调用者
            throw new RuntimeException("获取连接失败啦", e);
        }
    }
    //获取连接
    public static Connection getConnection() {
        try {
            Connection conn = tl.get(); //从ThreadLocal容器中获取链接
            if(conn == null) {
                String driver = pro.getProperty("mydriver");
                String url = pro.getProperty("url");
                String username = pro.getProperty("username");
                String password = pro.getProperty("password");
                //加载驱动类
                Class.forName(driver);
                //创建连接
                conn = DriverManager.getConnection(url, username, password);
                //将获取的连接存储在threadLocal容器中
                tl.set(conn);
            }
            return conn;
        } catch (Exception e) {
            e.printStackTrace();
            //讲出的异常转化成运行时异常通知调用者
            throw new RuntimeException("获取连接失败啦", e);
        }
    }

    public static void close(ResultSet rs, PreparedStatement pstm, Connection conn) {
        //释放资源
        try {
            if (rs != null) {
                rs.close();
            }
            if (pstm != null) {
                pstm.close();
            }
            if (conn != null) {
                conn.close();
                tl.remove();//将连接从threadLocal容器中清除掉
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
            throw new RuntimeException("关闭资源出错", throwables);
        }
    }
}

注意:在dao层不要关闭conn连接,service层才连接。

JDBC中分包分层

1、JDBC中分成三层

  • dao层:数据访问层,专注于数据库的增删改查。dao层分为6个步骤,不能关闭连接
  • service层:业务层,专注于实现用户业务需求,需要控制事务。service层分为5步。
  • view层:视图层,专注于给用户做展示的,同时接受用户输入的信息
    在这里插入图片描述

注意:方法调用过程中,必须逐级调用,不能跨层调用

2、分层的好处

(1) 各司其职,降低代码耦合度

(2) 便于分工,利于团队的协同开发

3、JDBC的分包

com.baizhi.hjq.conf:存储配置文件

com.baizhi.hjq.util:存储工具类,例如 JDBCUtil

------先测试连接-----

com.baizhi.hjq.test:测试存储内容

com.baizhi.hjq.sql:存储sql文件

com.baizhi.hjq.entity:存储实体类

com.baizhi.hjq.dao:先接口 再实现(接口:XxxDao 实现类“XxxDaoImpl“)

注意:dao中每个方法必须单独测试

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值