轻松驾驭JDBC:一篇文章帮你搞定数据库连接

推荐阅读

给软件行业带来了春天——揭秘Spring究竟是何方神圣(一)
给软件行业带来了春天——揭秘Spring究竟是何方神圣(二)



在这里插入图片描述

JDBC简介

Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。

JDBC通过一系列接口定义了访问数据库的通用API,不同的数据库厂商根据各自数据库的特点提供的对JDBC的实现(实现类包)。

JDBC基本原理

image.png

JDBC执行流程:

  • 连接数据源,如:数据库。
  • 为数据库传递查询和更新指令。
  • 处理数据库响应并返回的结果。

JDBC的架构

两层架构和三层架构

双层架构


作用:此架构中,Java Applet 或应用直接访问数据源。
条件:要求 Driver 能与访问的数据库交互。
机制:用户命令传给数据库或其他数据源,随之结果被返回。
部署:数据源可以在另一台机器上,用户通过网络连接,称为 C/S配置(可以是内联网或互联网)。

三层架构



该架构特殊之处在于,引入中间层服务。
流程:命令和结构都会经过该层。
吸引:可以增加企业数据的访问控制,以及多种类型的更新;另外,也可简化应用的部署,并在多数情况下有性能优势。

三层?

UI(表现层) :主要是与用户交互的界面,用户接受用户输入的数据和显示处理后用户需要的数据。
BLL(业务逻辑层):UI层和DAL层之间的桥梁。实现业务逻辑(验证,计算,业务规则等)。
DAL(数据访问层):与数据库打交道。主要实现对数据的增、删、改、查。将存储在数据库中的数据提交给业务层,同时将业务层处理的数据保存到数据库。(当然这些操作都是基于UI层的。用户的需求反映给界面(UI),UI反映给BLL,BLL反映给DAL,DAL进行数据的操作,操作后再一一返回,直到将用户所需数据反馈给用户).

image.png

Entity层

Entity(实体层):它不属于三层中的任何一层,但是它是必不可少的一层。
Entity在三层架构中的作用:

  • 1、实现面向对象思想中的"封装";
  • 2、贯穿于三层,在三层之间传递数据;(注:确切的说实体层贯穿于三层之间,来连接三层)
  • 3、每一层(UI—>BLL—>DAL)之间的数据传递(单向)是靠变量或实体作为参数来传递的,这样就构造了三层之间的联系,完成了功能的实现。
  • 对于初学者来说,可以这样理解:每张数据表对应一个实体,即每个数据表中的字段对应实体中的属性
  • (注:当然,事实上不是这样。为什么?1>,可能我们需要的实体在数据表对应的实体中并不存在;2>,我们完全可以将所有数据表中的所有字段都放在一个实体里)

注:这里为什么说可以暂时理解为每个数据表对应一个实体??
答:大家都知道,我们做系统的目的,是为用户提供服务,用户可不关心你的系统后台是怎么工作的,用户只关心软件是不是好用,界面是不是符合自己心意。用户在界面上轻松的增、删、改、查,那么数据库中也要有相应的增、删、改、查,而增删改查具体操作对象就是数据库中的数据,说白了就是表中的字段。所以,将每个数据表作为一个实体类,实体类封装的属性对应到表中的字段,这样的话,实体在贯穿于三层之间时,就可以实现增删改查数据了)

三层与实体层之间的依赖关系:
image.png

两层与三层的区别

两层:
image.png
(当任何一个地方发生变化时,都需要重新开发整个系统。"多层"放在一层,分工不明确耦合度高——难以适应需求变化,可维护性低、可扩展性低)
三层:
image.png
(发生在哪一层的变化,只需更改该层,不需要更改整个系统。层次清晰,分工明确,每层之间耦合度低——提高了效率,适应需求变化,可维护性高,可扩展性高)。
综上,三层架构的优势:

  • 结构清晰、耦合度低
  • 可维护性高,可扩展性高
  • 利于开发任务同步进行, 容易适应需求变化

劣势:

  • 降低了系统的性能。如果不采用分层式结构,很多业务可以直接造访数据库,以此获取相应的数据,如今却必须通过中间层来完成。
  • 有时会导致级联的修改。这种修改尤其体现在自上而下的方向。如果在表示层中需要增加一个功能,为保证其设计符合分层式结构,可能需要在相应的业务逻辑层和数据访问层中都增加相应的代码
  • 增加了代码量,增加了工作量

三层架构详解 | 菜鸟教程

JDBC使用步骤

  1. 导入jdbc操作
  2. 注册jdbc驱动
●   -参数:"驱动程序类名"(mysql,oracle等不同的驱动名)
●   -Class.forname("驱动程序类名")

//加载MySql驱动
Class.forName("com.mysql.jdbc.Driver")
  1. 获得Connection 对象
●  3个参数:URL,username,password连接数据库
String url="jdbc:mysql://127.0.0.1:3306/test";
String username="root";
String password="123456";
Connection conn= DriverManager.getConnection(url,username,password);
  1. 创建Statement()对象
用于执行SQL语句
execute() 可以执行任何SQL语句,但是常用于执行ddl,dcl语句
executeUpdate() 执行dml语句,如 insert,update delete
executeQuery() 执行DQL语句,如select

//创建 Statement
Statement st= conn.createStatement();
//执行DML语句
String dml="delete from k where age=24 ";
String dml ="delete from k"+" where age=26";
  1. 处理SQL执行结果:
● execute(DDL) 如果没有异常则成功。 
● executeUpdate(DML) 返回数字,表示更新的行数,抛出异常则失败。 
● executeQuery(DQL) 返回resultset(结果集)对象,代表二维查询结果,使用for遍历处理,如果查询失败抛出异常。

int b=st.executeUpdate(dml);
  1. 关闭数据连接
conn.close();

JDBC核心API

Statement

三个方法,分别执行三种语句,DQL,DML,DDL语句。

image.png
image.png

Statement st = conn.createStatement();
String ddl="create table a "+"(ids varchar(6))";
boolean b=st.execute(ddl);
System.out.println(b);

/这里的返回结果:
TRUE:表示有结果集,
* False:表示没有结果集
* 创建失败抛异常
* 在ddl语句执行如何判断结果,可以根据是否有没有异常,没有则创建成功。
* 在ddl语句中,创建表,实际上没有结果集返回,因为没有二维表返回,在这里并不是创建成功就是TRUE。但是如果执行select查询语句时,就有结果集返回,即为TRUE。
/

image.png
这里显示为FALSE,但是实际上是创建成功了。
不仅如此,进行表的删除等操作都是一样的。
image.png

注意点:

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

public class test {
    public static void main(String[] args) {
        try{
            Class.forName("com.mysql.jdbc.Driver");
            String url="jdbc:mysql://127.0.0.1:3306/test";
            String username="root";
            String password="123456";
            Connection conn= DriverManager.getConnection(url,username,password);
            //创建 Statement
            Statement st= conn.createStatement();
            //执行DML语句
            String dml="insert into k (name,id) values('cc',24) ";
            int b=st.executeUpdate(dml);
            System.out.println(b);
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

在idea返回结果

image.png
在数据库中查询的结果。
image.png

在拼接SQL语句时,一定要注意之间的空格,不然容易报错。

image.png

ResultSet
   ResultSet代表dql查询结果,是2维结果,其内部维护了一个读取数据的游标,默认情况下,游标在第一行数据之前,当调用next()方法时候,游标会向下移动,并将返回结果集中是否包含数据,如果包含数据就返回TRUE,结果集还提供了很好getXXX方法用于获取结果集游标指向当前行数据。

在这里插入图片描述

在这里插入图片描述

String sql="select * from k";
ResultSet rs=st.executeQurry(sql);
while(rs.next()){
String str=rs.getString("age"),
System.out.println(str);
}

image.png

可滚动结果集

image.png

ResultSetMetaData

ResultSetMetaData:数据结果集的元数据(结果集的相关数据,不是结果集本身)

Connection conn=null;
try{
conn=DBUtils.getConnection();
String sql="select * from k";
Statement st=conn.createStatement();
ResultSet rs=conn.executeQuery(sql);
//结果集元数据
ResultSetMetaData rsm =rs.getMetaData();
int n=rsm.getColumnCount();
for (int i=1;i<=n;i++){
    String name=metaData.getColumnName(i);
    System.out.println(name);
   }
}catch(Exception e){
   e.printStackTrace();
}finally{
 DUBtils.close(conn);
}

image.png

Properties

Properties 读取配置文件
properties 是Java中专门用于读取配置文件的API。

  • 其底层就是文本的API
  • Propertics 本身实现MAP接口,内部是散列表
  • Properties限定key和value都是String类型。

Properties常用的API方法

  • load(流) 读取一个配置文件
  • String getProperty(key)读取一个属性值

使用步骤:

  1. 创建properties对象
  2. 利用load方法读取配置文件
  3. 利用getproperty查询属性文件内容。

利用配置文件可以将程序中的参数保存到配置文件中,修改程序参数只需要修改配置文件即可。


        //1.创建properties 对象
        Properties pr=new Properties();
        System.out.println(pr);
        System.out.println(pr.size());

        //2.利用load方法读取文件
        InputStream in = day02.class.getClassLoader().getResourceAsStream("db.properties");
        //执行后,将文件内容读取到了散列表中
        pr.load(in);
        System.out.println(pr);
        System.out.println(pr.size());

        //查找 文件内容,就是读取文件内容

        System.out.println(pr.getProperty("jdbc.password"));

# db.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test
jdbc.user=root
jdbc.password=123456

image.png

PreparedStatement

image.png

preparedStatement 对象用于执行带参数的预编译执行计划。

image.png
image.png

  //带参数的SQL语句
 String sql="insert into k (name,age) values (?,?)";
// 将SQL发送到数据库,创建执行计划,返回值就是代表SQL语句在服务器的执行计划。
PreparedStatement ps=conn.prepareStatement(sql);
//替换执行计划中的参数,2个参数,顺序不可错,按照序号发送参数。
ps.setString(1,"bb");
 ps.setInt(2,21);
//执行执行计划
int n=ps.executeUpdate();
System.out.println(n);

预防SQL

SQL注入的产生

public class login {
    public static void main(String[] args){
        //获取用户输入
        Scanner in=new Scanner(System.in);
        System.out.println("用户名:" );
        String name=in.nextLine();
        System.out.println("密码:");
        String pwd=in.nextLine();
        //检查登录情况
        boolean pass=login(name,pwd);
        if (pass){
            System.out.println("欢迎你!"+name);
        }else{
            System.out.println("用户名或者密码错误!");
        }
    }
    //检查用户是否能够登录
public static boolean login(String name,String pwd){
 String sql="select * from h where name=\'"+name+"\'"+"and pwd=\'" +pwd+"\' ";
        System.out.println(sql);
        Connection conn=null;
        try {
            conn=DBUtils.getConnection();
            Statement st= conn.createStatement();
            ResultSet rs=st.executeQuery(sql);
            while (rs.next()){
                int  a=rs.getInt("id");
                return a>=1;
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            DBUtils.close(conn);
        }
        return false;
    }
}

image.png

SQL注入的原因

and 的优先级高于or ,所以当前面的FALSE and FALSE,即为 FALSE,但是 or 后面为TRUE,所以FALSE or TRUE,最终结果为TRUE。
用户输入了含有SQL成分的语句的参数,参数在拼接SQL时候造成了SQL语句的语义改变,改变了SQL语句的执行计划!

防守手段:
拦截用户输入的SQL成分。
select * from h where name=? and pwd =?
这种情况,or 只是一个普通的字符串,不当成SQL成分
这种就可以利用preparedStatement。

public class right {
    public static void main(String[] args){
        Scanner in=new Scanner(System.in);
        System.out.println("用户名:");
        String name=in.nextLine();
        System.out.println("密码:");
        String pwd=in.nextLine();
        boolean pass=right(name,pwd);
        if (pass){
            System.out.println("登录成功");
        }else {
            System.out.println("登陆失败");
        }

    }

    public static boolean right(String name,String pwd){
        Connection conn=null;
        String sql="select * from h "+" where name=? and pwd=? ";
        System.out.println(sql);

        try {
            conn=DBUtils.getConnection();
            PreparedStatement pr= conn.prepareStatement(sql);
            pr.setString(1,name);
            pr.setString(2,pwd);
            ResultSet rs=pr.executeQuery();
            while (rs.next()){
                int n=rs.getInt("id");
                return n>=1;
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            DBUtils.close(conn);
        }
        return false;
    }
}

image.png

连接管理

在软件中数据库连接使用非常频繁,如果每次都创建连接,就会造成代码的大量冗余,常规做法是建立数据库连接工具类,封装数据库连接流程,统一数据库连接过程,使用时候可以简化代码。

image.png
实现步骤:

  1. 创建数据库连接参数文件,db.properties
  2. 创建DBUtils.java 封装数据库连接方法
  • 利用properties读取配置文件夹中的数据库连接参数

  • 创建方法 getConnection方法封装数据库连接过程。

    3.使用getConnection()连接数据库

public class DBUtils {

        static String driver;
        static String url;
        static String user;
        static String password;
       //静态代码块的目的是从

       static {
        //初始化静态属性
        /*1.利用properties读取配置文件
        * 2.从配置文件中查找相应参数值
        * */

       try{
      Properties pr=new Properties();
      InputSteam in=DBUtils.class.getClassLoader().getResourceAsStream("db.properties");
      pr.load(in);
      System.out.println(pr);
//初始化数据
      driver=pr.getProperty("jdbc.driver");
      url=pr.getProperty("jdbc.url");
      password=pr.getProperty("jdbc.password");
      user=pr.getProperty("jdbc.user");

      in.close();

       }catch(IOException e){
      e.printStackTrace();
       }

       }
    /*封装创建数据库连接的过程,简化数据库的连接*/
    public static Connection getConnection(){
        try {
        Class.forName(driver);
        Connection conn=DriverManager.getConnection(url,user,password);
            return conn;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }

    }


    /*
     * dbutils.java
     * 关闭数据库连接方法,封装复杂的关闭过程
     * */
public static void close(Connection conn){
        if (conn!=null){
            try {
                conn.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

这样可以简化重复操作。

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test
jdbc.user=root
jdbc.password=123456
//创建连接
Connection conn=DBUtils.getConnection();
//创建Statement()对象
Statement st=conn.creataStatement();
String st="select *from k";
//查询结果
ResultSet rs=st.executeQuery(sql);
while(rs.next()){
String name=rs.getString("name");
System.out.println("name: "+name);
}
conn.close();

DriverManager 管理数据库连接适合单线程情况,而在多线程并发情况下,为了能够重用数据库连接,同时控制并发连接总数,保护数据库避免连接重载,一定要使用数据库连接池。

连接池技术

image.png

数据库连接池的开源实现非常多,dbcp是常用的连接池之一。
导入dbcp。
image.png
导包
从模块中搜索对应的包,然后导入时,点击应用后,在确定。
image.png

import org.apache.commons.dbcp2.BasicDataSource;
public class day03 {
    public static void main(String[] args)

    throws Exception{
        String driver="com.mysql.jdbc.Driver";
        String url="jdbc:mysql://127.0.0.1:3506/test";
        String password="123456";
        String user="root";

       BasicDataSource ds=new BasicDataSource();
       ds.setDriverClassName(driver);
       ds.setUrl(url);
       ds.setUsername(user);
       ds.setPassword(password);
       //设置连接池的管理参数
        ds.setInitialSize(2);
        ds.setMaxTotal(100);
        //使用连接池中的数据库连接
        Connection conn=ds.getConnection();
        //执行SQL
        String sql="select *from k";
        Statement st=conn.createStatement();
        ResultSet rs=st.executeQuery(sql);
        while(rs.next()){
            String str=rs.getString("age");
            System.out.println(str);
        }
        //归还连接到数据库连接池
        conn.close();     
    }
}
try{
conn=DBUtils.getConnection();
String sql="select * from k";
Statement st=conn.createStatement();
ResultSet rs=st.executeQuery(sql);
while(rs.next()){
String str=rs.getString("name");
System.out.println(str);
}
rs.close();
st.close();
}

连接并发测试
public class day05 {
    public static void main(String[] args){
        Thread t1=new TestTherad(4000);
        Thread t2=new TestTherad(5000);
        Thread t3=new TestTherad(1000);
        t1.start();
        t2.start();
        t3.start();
    }
}
class TestTherad extends Thread{
    int wait;

    public TestTherad(int wait){
        this.wait=wait;
    }

    public void run(){
        Connection conn=null;
        try {
            conn=DBUtils.getConnection();
            System.out.println("获取连接:"+conn);
            Thread.sleep(wait);
            String sql="select * from k";
            Statement st=conn.createStatement();
            ResultSet rs=st.executeQuery(sql);
            while(rs.next()){
                System.out.println(rs.getString("name"));
            }
            System.out.println(wait+"结束");

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            DBUtils.close(conn);
        }
    }
}

image.png

JDBC事务

事务(Transaction) :数据库中保证交易可靠的机制。
计算机中使用到原子,基本是指某项不能在分或打开的。即不可再分的。例如:交易操作,即借钱中,一个账户的扣除和另一个账户的增加必须同时进行,这中间操作不可再分了。

image.png
image.png

隔离性演示:

image.png

如数据库未执行完,并未进行commit,那么对于同一影响的数据就不能够被操作,会在执行过程中类似于卡住,这是因为数据进行了加锁。(oracle数据库)

事务API

image.png

conn.setAutoCommit(false);//开启事务
conn.commit();//提交事务
conn.rollback();//回滚事务

交易测试案例
public class test {
    public static void main(String[] args) {
        pay("bob","cindy",100);
        pay("alice","bob",-100);
    }
    public static void pay(String from ,String to,double money ){
        String sql1="update k set balance=balance+ ? where name=? ";
        String sql2="select balance from k where name= ? ";
        Connection conn=null;
        try{
            conn=DBUtils.getConnection();
            conn.setAutoCommit(false);
            PreparedStatement ps=conn.prepareStatement(sql1);
            ps.setDouble(1,-money);
            ps.setString(2,from);
            int n=ps.executeUpdate();
            if (n!=1){
                throw new Exception("error");
            }
            //加余额
            ps.setDouble(1,money);
            ps.setString(2,to);
            n=ps.executeUpdate();
            if (n!=1){
                throw new Exception("ok");
            }
            ps.close();
            //检查
            ps=conn.prepareStatement(sql2);
            ps.setString(1,from);
            ResultSet resultSet=ps.executeQuery();
            while (resultSet.next()){
                double balance=resultSet.getDouble(1);
                if (balance<0){
                    throw new Exception("nonono");
                }
            }
            conn.commit();

        }catch (Exception e){
            e.printStackTrace();
            try {
                conn.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }finally {
            DBUtils.close(conn);
        }
    }
}

JDBC批量处理

一个批处理是被发送到数据库以作为单个单元执行的一组更新语句。

批处理API

image.png

executeBatch()

image.png

int[] executeBatch() throws SQLException;

数组值可能是以下之一:

  • 大于或等于零的数字,表示命令已成功处理,并且是更新计数,给出了数据库中受命令影响的行数执行

image.png

  • SUCCESS_NO_INFO **( -2)**的值,表示命令为处理成功,但受影响的行数为未知
  • 如果批量更新中的命令之一无法正确执行,此方法引发BatchUpdateException,JDBC driver可能会也可能不会继续处理剩余的命令。但是driver的行为是与特定的DBMS绑定的,要么总是继续处理命令,要么从不继续处理命令。如果驱动程序继续处理,方法将返回 EXECUTE_FAILED(-3)
批量执行SQL语句
public class ddl {
    public static void main(String[] args){
        String sql1="create table log_1 (id int(8),msg varchar(100))";
        String sql2="create table log_2 (id int(8),msg varchar(100))";

        Connection conn=null;
        try {
            conn= DBUtils.getConnection();
            Statement st=conn.createStatement();
            st.addBatch(sql1);
            st.addBatch(sql2);
            //执行一批SQL语句
            int [] ary=st.executeBatch();
            System.out.println(Arrays.toString(ary));
            System.out.println("ok");

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            DBUtils.close(conn);
        }
    }
}

image.png

批量参数处理
import test.DBUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.Arrays;
public class test {
    public static void main(String[] args){
        String sql="insert into k (name,age,balance) values(?,?,?)";
        Connection conn=null;
        try {
            conn= DBUtils.getConnection();
            PreparedStatement ps=conn.prepareStatement(sql);
            for (int i=0;i<5;i++){
                //替换参数
                ps.setString(1,"doc");
                ps.setInt(2,25+i);
                ps.setInt(3,800);
                //将参数添加到ps缓存区
                ps.addBatch();
            }
            //批量执行
            int [] ary=ps.executeBatch();
            System.out.println(Arrays.toString(ary));
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            DBUtils.close(conn);
        }
    }
}

防止OutOfMemory错误

如果Preparedstatement 对象中的SQL列表包含过多的待处理SQL语句可能会产生OutOfMemory错误。
可以采取分批次执行,可以避免内存溢出问题

if ((i+1)%4==0){
ps.executeBatch();
ps.clearBatch();
 }
}

image.png

返回自动主键

jdbc API 提供了返回插入数据期间自动生成的id的值,
image.png

public class test {
    public static void main(String[] args){
        Connection connection=null;

        try {
            connection= DBUtils.getConnection();
            connection.setAutoCommit(false);
            String sql="insert into keywords (word) values(?)";
            //自动生成序号的列名
            String[] cols={"id"};
            PreparedStatement ps=connection.prepareStatement(sql,cols);
            ps.setString(1,"hello");
            int n=ps.executeUpdate();
            if (n!=1){
                throw new Exception("error");
            }
            //获取自动生成的id
            ResultSet rs=ps.getGeneratedKeys();
            int id=-1;
            while (rs.next()){
                id=rs.getInt(1);
            }
            rs.close();
            ps.close();
            String sql2="insert into post (content,kid) values(?,?)";
            ps=connection.prepareStatement(sql2);
            ps.setString(1,"world");
            ps.setInt(2,id);
            n=ps.executeUpdate();
            if (n!=1){
                throw  new Exception("good");
            }
            connection.commit();
            System.out.println("ok");
        }catch(Exception e){
            e.printStackTrace();
            DBUtils.rollback(connection);
        }finally {
            DBUtils.close(connection);
        }
    }
}

实现分页查询

image.png

数据库连接是编程中不可或缺的一环,通过本文你已经了解了如何轻松驾驭JDBC。然而,这仅仅是一个开始。深入学习数据库管理、优化查询、事务处理等方面的知识,将为你打开更广阔的世界。

感谢阅读本文,如有任何问题,欢迎在评论区积极讨论。

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天马行空的程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值