JDBC

JDBC(Java DataBase Connectivity )概念:Java数据库连接,就是通过Java语言操作数据库。

JDBC本质: 其实是官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的m88官方是驱动jar包中的实现类。

大致关系图如下:


JDBC快速入门

使用JDBC一般步骤如下:

  1. 导入驱动jar包(不同版本MySQL的jar包需求不同)

我所用的MySQL是5.5版本的,所以我用的是5.x版本的jar包,如:mysql-connector-java-5.1.37-bin.jar

我们要把我们jar加入到我们的项目中,操作步骤:

①复制mysql-connector-java-5.1.37-bin.jar到项目的libs目录下

②右键-->Add As Library

  1. 注册驱动
  2. 获取数据库连接对象 Connection
  3. 定义sql
  4. 获取执行sql语句的对象 Statement
  5. 执行sql,接受返回结果
  6. 处理结果
  7. 释放资源(资源不释放会占用资源,该现象称为内存泄露)

m88官方如下:

我想把经济部改为服务部

public static void main(String[] args) {
      Connection connection=null;
      Statement statement=null;
      try {
         //1. 导入驱动jar包
         //2.注册驱动
         Class.forName("com.mysql.jdbc.Driver");
         //3.获取数据库连接对象
         connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/dl1", "root", "root");
         //4.定义sql语句
         String sql="update dept set name='经济部' where id=1";
         //5.获取执行sql的对象 Statement
         statement = connection.createStatement();
         //6.执行sql
         int i = statement.executeUpdate(sql);
         //7.处理结果
         System.out.println(i);
      } catch (Exception e) {
         e.printStackTrace();
      }finally {
         //8.释放资源
         try {
            statement.close();
         } catch (SQLException e) {
            throw new RuntimeException(e);
         }
         try {
            connection.close();
         } catch (SQLException e) {
            throw new RuntimeException(e);
         }
      }
   }

结果如下

接下来我来讲一下上面出现的对象详解各个对象

DriverManager:驱动管理对象

功能:

  1. 注册驱动:告诉m88体育该使用哪一个数据库驱动jar

方法

说明

static void registerDriver(Driver driver)

注册与给定的驱动m88体育 DriverManager

但我们注册驱动写的m88官方为:Class.forName("com.mysql.jdbc.Driver");

我们查看源码可以发现发现:在com.mysql.jdbc.Driver类中存在静态m88官方块,里面使用了这个方法,所以我们只需把Driver类的m88官方加载进内存就行

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

注意:mysql5之后的驱动jar包可以省略注册驱动的步骤。

因为我们我们导入的jar包中的java.sql.Driver中有com.mysql.jdbc.Driver 这一行内容,我们不注册驱动的话内部会自己找到这个文件注册驱动的。

  1. 获取数据库连接:

DriverManager方法

说明

static Connection getConnection(String url, String user, String password)

获取数据库连接对象

参数一:url:指定连接的路径

语法:jdbc:mysql://ip地址(域名):端口号/数据库名称

例:jdbc:mysql://localhost:3306/db1

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

jdbc:mysql:///数据库名称

参数二:user:用户名

参数三:password:密码


Connection:数据库连接对象

数据库连接对象Connection其实就是Javam88官方和数据库之间的桥梁对象

功能

  1. 获取执行sql 的对象(Statement和PreparedStatement)

Connection方法

说明

Statement createStatement()

获取Statement对象

PreparedStatement prepareStatement(String sql)

获取PreparedStatement对象

  1. 管理事务:

Connection方法

说明

setAutoCommit(boolean autoCommit)

设置参数为false,开启事务

commit()

提交事务

rollback()

回滚事务


Statement:执行sql的对象

Statement执行sql的方法

说明

boolean execute(String sql)

可以执行任意的sql(了解)

int executeUpdate(String sql)

执行DML(insert、update、delete)语句、DDL(create,alter、drop)语句

ResultSet executeQuery(String sql)

执行DQL(select)语句

注意:int executeUpdate(String sql) 该方法的返回值:影响的行数,可以通过这个影响的行数判断DML语句是否执行成功,返回值>0的则执行成功,反之,则失败。

练习:

操作accout表,如下:

  1. account表 添加一条记录
public static void main(String[] args) throws SQLException {
      Connection conn=null;
      Statement stmt=null;
      try {
         Class.forName("com.mysql.jdbc.Driver");
          conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/dl1", "root", "root");
          stmt = conn.createStatement();
         String sql="insert into account values (null,'王五',null)";
         int i = stmt.executeUpdate(sql);
         System.out.println(i);
      } catch (Exception e) {
         e.printStackTrace();
      }finally {
         try {
            stmt.close();
         } catch (SQLException e) {
            throw new RuntimeException(e);
         }
         conn.close();
      }
   }

操作后表的变化

  1. account表 修改记录,把王五的balance变为1000,只需把sql语句改一下,其他的不用变
String sql="update account set balance=1000 where id=3";

操作后表的变化

  1. account表 删除一条记录

改一下sql为

String sql="delete from account where id=3";

操作后表的变化


ResultSet:结果集对象,封装查询结果

前面我们提到的Statement的一个方法:ResultSet executeQuery(String sql) 返回的结果即为结果集对象(ResultSet),因为我们查询的是一张表的结果,这时会出现一个光标,也叫做游标。

Resultset光标最初位于第一行之前,第一次调用方法next()使第一行成为当前行;第二次调用使第二行成为当前行,依此类推。

当调用next方法返回false时,光标位于最后一行之后。

ResultSet方法

说明

boolean next()

游标向下移动一行,判断当前行是否是最后一行末尾(即是否有数据),如果是,则返回false,如果不是则返回true

我们让光标下移的目的是为了得到数据,我们获取数据的方法为

ResultSet方法

说明

getXxx(参数)

获取数据

  • Xxx:代表数据类型。【如:数据为int类型,方法为getInt(),为String字符串,方法为:getString()】

该方法的参数有两种数据类型:

  • int:代表列的编号,从1开始 如: getString(1):获得改行数据的第一个数据
  • String:代表列名称。 如: getDouble("balance"):获得改行字段对应的数据

使用步骤:

  1. 游标向下移动一行
  2. 判断是否有数据
  3. 获取数据

练习:

  • 定义一个方法,查询emp表的数据将其封装为对象,然后装载集合,返回。

步骤:

  1. 定义Emp类
  2. 定义方法 public static List findAll(){}
  3. 实现方法 select * from emp;

Empm88官方(对应的getter,setter和toString方法自己写)

public class Emp {
    private int id;
    private String name;
    private String gender;
    private double salary;
    private Date join_date;
    private int dept_id;
    }

操作m88官方

public static void main(String[] args){
         List<Emp> list=findAll();
      for (Emp emp : list) {
         System.out.println(emp);
      }

   }
   public static List<Emp> findAll(){
       ArrayList<Emp> emps=new ArrayList<>();
      Connection conn=null;
      Statement stmt=null;
      ResultSet rs=null;
      try {
         Class.forName("com.mysql.jdbc.Driver");
         conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/dl1", "root", "root");
         stmt = conn.createStatement();
         String sql="select*from emp";
         rs = stmt.executeQuery(sql);
         while (rs.next()){
            Emp emp=new Emp();
            emp.setId(rs.getInt("id"));
            emp.setName(rs.getString(2));
            emp.setGender(rs.getString(3));
            emp.setSalary(rs.getDouble("salary"));
            emp.setJoin_date(rs.getDate("join_date"));
            emp.setDept_id(rs.getInt("dept_id"));
            emps.add(emp);
         }
      } catch (Exception e) {
         e.printStackTrace();
      }finally {
         try {
            rs.close();
         } catch (SQLException e) {
            throw new RuntimeException(e);
         }
         try {
            stmt.close();
         } catch (SQLException e) {
            throw new RuntimeException(e);
         }
         try {
            conn.close();
         } catch (SQLException e) {
            throw new RuntimeException(e);
         }
      }
   return emps;
   }

PreparedStatement:执行sql的对象

前面我们已经提到过获取该对象的方法

方法

说明

PreparedStatement prepareStatement(String sql)

获取PreparedStatement对象

PreparedStatement继承了Statement接口,所以功能比Statement更强大

我们以后会经常使用PreparedStatement,因为Statemenet会出现SQL注入问题

SQL注入问题:在拼接sql时,有一些sql的特殊关键字参与字符串的拼接。会造成安全性问题.

比如用户登陆时:输入用户为任意值,输入密码:'a' or 'a' = 'a'

select * from user where username = 'fhdsjkf' and password = 'a' or 'a' = 'a'

使用PreparedStatement对象就可以解决sql注入问题

我们写的SQL就为预编译的SQL:参数使用 “?” 作为占位符

PreparedStatement的操作步骤:

  1. 导入驱动jar包 mysql-connector-java-5.1.37-bin.jar
  2. 注册驱动
  3. 获取数据库连接对象 Connection
  4. 定义sql注意:sql的参数使用?作为占位符。 如:
select * from user where username = ? and password = ?;
  1. 获取执行sql语句的对象 PreparedStatement Connection.prepareStatement(String sql)
  2. 给?赋值:
  • 方法: setXxx(参数1,参数2)
  • 参数1:?的位置编号 从1 开始
  • 参数2:?的值
  1. 执行sql,接受返回结果,不需要传递sql语句
  2. 处理结果
  3. 释放资源

操作m88官方如下:

public static void main(String[] args) {
        Connection conn=null;
        PreparedStatement pstmt=null;
        ResultSet rs=null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn= DriverManager.getConnection("jdbc:mysql:///dl1","root","root");
            String sql="select*from emp where id=? or id =?";
            pstmt=conn.prepareStatement(sql);
            pstmt.setInt(1,1);
            pstmt.setInt(2,2);
            rs = pstmt.executeQuery();
            while (rs.next()){
                System.out.println(rs.getInt(1)+rs.getString("NAME"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                rs.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
            try {
                pstmt.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
            try {
                conn.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }

    }

(后期都会使用PreparedStatement来完成增删改查的所有操作)

好处

  • 可以防止SQL注入
  • 效率更高

为什么使用PrepareStatement对象能解决SQL注入问题?

  • sql的执行需要编译,注入问题之所以出现,是因为用户填写 sql语句参与了编译
  • 使用PrepareStatement对象在执行sql语句时,会将sql语句“运送”到mysql上编译,再回到 java端拿到参数运送到mysql端,用户填写的sql语句,就不会参与编译,只会当做参数来看,避免了sql注入问题
  • PrepareStatement 在执行母句相同,参数不同的批量执行时,因为只会编译一次,节省了大量编译时间,效率会高

抽取JDBC工具类 : JDBCUtils

我们前面在写的时候发现有许多步骤都是一样的,为了节省一些操作,我们可以定义一个工具类,如果有需要用的地方直接调用就行了。

定义JDBCUtils的目的:简化书写

我们分析一下那些可以步骤可以把方法抽取出来:

把方法抽取出来:就是重构->抽取方法,简单来说就是把部分相关m88官方抽取出来成为一个方法,原来m88官方的位置改由调用该方法来代替

  1. 注册驱动
  2. 获取连接对象
  • 需求:不想传递参数(麻烦),还得保证工具类的通用性。
  • 解决:写一个配置文件

jdbc.propertiesurl= user= password=

如:

  1. 释放资源

m88官方展示:

public class JDBCUtils {
    static String url=null;
    static String user=null;
    static String password=null;
            static {
                try {
                    Properties properties=new Properties();
                    properties.load(JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties"));
                    url=properties.getProperty("url");
                    user=properties.getProperty("user");
                    password=properties.getProperty("password");
                    //注册驱动
                    Class.forName(properties.getProperty("driver"));
                } catch (IOException e) {
                    throw new RuntimeException(e);
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e);
                }
            }
	//获取连接对象
    public static Connection getConnection() throws SQLException {
             return DriverManager.getConnection(url,user,password);
    }
    //释放资源
    public static void close(Statement stmt, Connection conn){
       close(null,stmt,conn);
    }
    public static void close(ResultSet rs,Statement stmt,Connection conn){
       if (rs!=null) {
           try {
               rs.close();
           } catch (SQLException e) {
               throw new RuntimeException(e);
           }
       }
        if (rs!=null) {

            try {
                stmt.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if (rs!=null) {
            try {
                conn.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

JDBC控制事务

我们上一篇文章已经说过了事务是什么,我们简单说一下

事务:一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。

事务操作:

  1. 开启事务
  2. 提交事务
  3. 回滚事务

上面说过使用Connection对象来管理事务的方法

Connection方法

说明

setAutoCommit(boolean autoCommit)

设置参数为false,开启事务

commit()

提交事务

rollback()

回滚事务

一般在catch中进行回滚事务操作

public static void main(String[] args) {
        Connection conn=null;
        Statement stmt=null;
        try {
            conn=JDBCUtils.getConnection();
            conn.setAutoCommit(false);
            String sql="update account set balance=1500";
            stmt=conn.createStatement();
            int i = stmt.executeUpdate(sql);
            System.out.println(i);
            System.out.println(10/0);
            conn.commit();
        } catch (Exception e) {
            try {
                conn.rollback();
            } catch (SQLException ex) {
                throw new RuntimeException(ex);
            }
            e.printStackTrace();
        }finally {
            JDBCUtils.close(stmt,conn);
        }
    }

数据库连接池

概念:

  • 其实就是一个容器(集合),存放数据库连接的容器。
  • 当官方初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器。

好处:

  1. 节约资源
  2. 用户访问高效

实现:

标准接口:DataSource(在javax.sql包下)

方法:

方法

说明

getConnection()

获取连接

close()

归还连接

注意:如果连接对象Connection是从连接池中获取的,那么调用close() 方法,则不会再关闭连接了,而是归还连接。

(一般我们不去实现它,有数据库厂商来实现,我们说两种数据库连接池体育)

  1. C3P0:数据库连接池体育
  2. Druid:数据库连接池实现体育,由阿里巴巴提供的

C3P0:数据库连接池体育

使用步骤:

  1. 导入jar包 (两个) c3p0-0.9.5.2.jar mchange-commons-java-0.2.12.jar(注意:不要忘记导入数据库驱动jar包)
  2. 定义配置文件:
  • 名称: c3p0.properties 或者 c3p0-config.xml
  • 路径:直接将文件放在src目录下即可。
  1. 创建核心对象,即数据库连接池对象:ComboPooledDataSource
  2. 获取连接: getConnection

相关m88官方:

创建数据库连接池对象

DataSource ds  = new ComboPooledDataSource();
  1. 获取连接对象
Connection conn = ds.getConnection();

Druid:数据库连接池实现体育,由阿里巴巴提供的

使用步骤:

  1. 导入jar包 druid-1.0.9.jar
  2. 定义配置文件:
  • 是properties形式的
  • 可以叫任意名称,可以放在任意目录下
  1. 加载配置文件
  2. 获取数据库连接池对象:通过工厂类来获取 DruidDataSourceFactory
  3. 获取连接:getConnection

定义工具类

步骤:

  1. 定义一个类 JDBCUtils
  2. 提供静态m88官方块加载配置文件,初始化连接池对象
  3. 提供方法

把方法抽取出来

  1. 获取连接方法: 通过数据库连接池获取连接
  2. 释放资源
  3. 获取连接池的方法

JDBCUtils工具类m88官方

public class JDBCUtils {
    private static DataSource ds=null;
    static {
        try {
            Properties p=new Properties();
            p.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
            ds=DruidDataSourceFactory.createDataSource(p);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }
    public static void close(Statement stmt,Connection conn){
        close(null,stmt,conn);
    }
    public static void close(ResultSet rs, Statement stmt,Connection conn){
        try {
            if (rs!=null) {
                rs.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        try {
            if (stmt!=null) {
                stmt.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        try {
            if (conn!=null) {
                conn.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    public static DataSource getDataSource(){
        return ds;
    }
}

为什么要使用数据库连接池

  • 数据库连接创建是一个非常昂贵的操作,我们应该尽可能避免频繁创建连接。
  • 传统的数据库访问方式,每次操作数据库都需要打开、关闭物理连接,非常耗费官方资源,性能差。而且数据库支持的连接数是有限的,创建大量的连接会导致数据库僵死。
  • 使用连接池,官方初始化后会创建容器,会申请一些连接对象,当需要访问数据库时,从容器中取连接对象直接使用,数据库操作结束后归还给容器。
  • 优点是节约资源,提高访问数据库的性能

Spring JDBC:JdbcTemplate

Spring框架对JDBC的简单封装。提供了一个JDBCTemplate对象简化JDBC的會員註冊

使用步骤:

  1. 导入jar包(相关jar包可以点击下方链接下载)

链接:

  1. 创建JdbcTemplate对象。依赖于数据源DataSource(我们上面定义的工具类就有用了)
JdbcTemplate template = new JdbcTemplate(ds);
  1. 调用JdbcTemplate的方法来完成CRUD的操作

方法名称

说明

update()

执行DML语句。增、删、改语句

queryForMap()

查询结果将结果集封装为map集合,将列名作为key,将值作为value 将这条记录封装为一个map集合

queryForList()

查询结果将结果集封装为list集合

query()

查询结果,将结果封装为JavaBean对象

queryForObject()

查询结果,将结果封装为对象(一般用于聚合函数的查询)

query()中有一个参数:RowMapper

  • 一般我们使用BeanPropertyRowMapper实现类。可以完成数据到JavaBean的自动封装(直接new一个)

new BeanPropertyRowMapper<类型>(类型.class)

我们先创建一个JdbcTemplate对象

JdbcTemplate template=new JdbcTemplate(JDBCUtils.getDataSource());

我接下来说一下这些方法的大致使用

update()

@Test
     public void test1(){
         //2. 定义sql
         String sql = "...";
         //3. 执行sql
         int count = template.update(sql);
         System.out.println(count);
     }

update()(?作为占位符)

@Test
public void test2(){
       String sql = "insert into emp(id,ename,dept_id) values(?,?,?)";
       int count = template.update(sql, 第一个?的值, 第二个?的值, 第三个?的值);
       System.out.println(count);
   }

queryForMap()

@Test
   public void test3(){
       String sql = "select * from emp where id = ? or id = ?";
       Map<String, Object> map = template.queryForMap(sql,  第一个?的值, 第二个?的值);
       System.out.println(map);
       //结果为这种形式
       //{id=1001, ename=孙悟空, job_id=4, mgr=1004, joindate=2000-12-17, salary=10000.00, bonus=null, dept_id=20}
   }

queryForList()

@Test
  public void test4(){
      String sql = "select * from emp";
      List<Map<String, Object>> list = template.queryForList(sql);
      for (Map<String, Object> stringObjectMap : list) {
          System.out.println(stringObjectMap);
      }
  }

query()(参数用RowMapper)

@Test
    public void test5(){
        String sql = "select * from emp";
        List<Emp> list = template.query(sql, new RowMapper<Emp>() {

            @Override
            public Emp mapRow(ResultSet rs, int i) throws SQLException {
                Emp emp = new Emp();                 
                emp.setId(rs.getInt("id"));
                emp.setEname(rs.getString("ename"));
                emp.setJob_id(rs.getInt("job_id"));
                emp.setMgr(rs.getInt("mgr"));
                emp.setJoindate(rs.getDate("joindate"));
                emp.setSalary(rs.getDouble("salary"));
                emp.setBonus(rs.getDouble("bonus"));
                emp.setDept_id(rs.getInt("dept_id");
                return emp;
            }
        });
        for (Emp emp : list) {
            System.out.println(emp);
        }
    }

query()(参数用BeanPropertyRowMapper)

@Test
public void test5_2(){
    String sql = "select * from emp";
    List<Emp> list = template.query(sql, new BeanPropertyRowMapper<Emp>(Emp.class));
    for (Emp emp : list) {
        System.out.println(emp);
    }
}

queryForObject()

@Test
   public void test6(){
       String sql = "select count(id) from emp";
       //sql语句count(列名)返回的值是long型值
       Long total = template.queryForObject(sql, Long.class);
       System.out.println(total);
   }