JavaEE开发-第三节 JDBC

JDBC简介

如何通过Java操作数据库?

  1. JDBC,全称是Java Database Connectivity
    1. 在Java中用来规范如何访问关系型数据库,由各大数据库厂商去实现它
    2. 属于JavaSE的一部分
    3. 简单来说就是一套接口规范,定义了一些访问数据库的接口标准
    4. 各大厂商需要实现这些接口标准
    5. JDBC是JDK的一部分,只要安装了JDK,就有JDBC
  2. 调用方式
    1. java开发人员,使用JDBC接口,调用各大厂商的数据库

    图1

下载MySQL的JDBC实现(jar,驱动包)

  1. 8.0版本的驱动包同时支持MySQL 5.7/8.0
    1. https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-versions.html
  2. 下载地址
    1. https://dev.mysql.com/downloads/connector/j/
    2. 进入后点击Archive,选择版本号,Operating System选择Platmform Independent 然后点击(mysql-connector-java-8.0.21.zip)Download,即可
    3. 然后解压,拿到解压文件夹中的:mysql-connector-java-8.0.21.jar
  3. IDEA如何手动导入第三方jar包(纯java项目):
    1. 新建一个纯java项目:File->new->module->选择java->点击next,命名->点击OK
    2. 在项目目录下新建一个与src同级的lib文件夹

       //纯java项目,非javaEE项目
       03_JDBC
           lib
           src
           03_JDBC.iml
      
    3. 复制jar包到lib目录下
    4. 右击jar包->添加为库(add as labrary)->level:选择模块库(Module Library) ->点击确定(OK)

JDBC使用步骤

  1. 将Driver (驱动程序) 注册到DriverManager (驱动程序管理者)
    1. Driver指的是各大数据库厂商实现的驱动,在上面下载的jar包的com.mysql.jdbc文件夹下有个Driver类
    2. DriverManager指的是JDBC实现的驱动程序管理者
  2. 利用DriverManager创建Connection(数据库连接)
  3. 利用Connection创建Statement(语句)
  4. 利用Statement执行SQL语句
  5. 关闭资源(关闭Statement、Connection等)

JDBC细节

  1. MySQL的url格式是
    1. jdbc:mysql://IP地址:端口号/数据库名
    2. 比如:jdbc:mysql://localhost:3306/zh
  2. 驱动类

               MySQL驱动包6.x以前           MySQL驱动包6.x开始
     驱动类     com.mysql.jdbc.Driver       com.mysql.cj.jdbc.Driver
     时区                                  需要指定一个确定的时区
                                          比如在url后加上参数serverTimezone=UTC                        
    
  3. 6.x以后代码举例:

     Class.forName("com.mysql.cj.jdbc.Driver");
     DriverManager.getConnection("jdbc:mysql://localhost:3306/zh_info?serverTimezone=UTC","root","root");
    

代码举例

public class Main {
    public static void main(String[] args) throws Exception {
        //1. 注册Driver到DriverManager
        //DriverManager.registerDriver(new Driver());
        //装载一个类,就是将这个Driver类装载进JVM去,通过查看Driver类的源代码,发现有个静态方法直接实现了上面的一句代码,因此当JVM第一次装载这个Driver类时就会调用。下面这么写等价于上面
        //而且这么写的好处是不需要导入Driver类,即import com.mysql.jdbc.Driver;
        Class.forName("com.mysql.jdbc.Driver");
        //2. 利用DriverManager创建Connection(数据库连接)
        //url指的是mysql的服务器地址,但是注意协议是 jdbc:mysql:
        Connection connection =  DriverManager.getConnection("jdbc:mysql://localhost:3306/zh_info","root","root");
        //3. 利用Connection创建Statement(语句)
        Statement statement = connection.createStatement();
        //4. 利用Statement执行SQL语句
        //将id==1的学生年龄改为100
        statement.execute("UPDATE  student SET age = 100 WHERE  id =1");
        //5. 关闭资源(关闭Statement、Connection等)
        statement.close();
        connection.close();
    }
}
  1. 为什么要使用Class.forName("com.mysql.jdbc.Driver");代替DriverManager.registerDriver(new Driver());?
    1. Class.forName("com.mysql.jdbc.Driver");的本质是装载一个类
    2. 查看mysql的驱动源码com.mysql.jdbc.Driver类的实现如下

       public class Driver extends NonRegisteringDriver implements java.sql.Driver {
           // Register ourselves with the DriverManager
           static {
               try {
                   java.sql.DriverManager.registerDriver(new Driver());
               } catch (SQLException E) {
                   throw new RuntimeException("Can't register driver!");
               }
           }
              
           /**
            * Construct a new driver and register it with DriverManager
            * @throws SQLExceptionif a database error occurs.
           */
           public Driver() throws SQLException {
               // Required for Class.forName().newInstance()
           }
       }
      
    3. 会发现有个static块,当Driver类被加载的时候会自动调用static中的代码,因此可以替换
    4. 还有一个好处,这么一些文件中就不需要导入Driver类,即import com.mysql.jdbc.Driver;,那么即使有换掉数据库,只需要把字符串换掉即可

JDBC版本

  1. 从JDBC 4.0开始, 显式注册驱动程序是可选的

     //显示注册
     DriverManager.registerDriver(new Driver());
     Class.forName("com.mysql.jdbc.Driver");
    
  2. 我们只需要将供应商的Jar放在类路径中,然后DriverManager就可以自动检测并加载驱动程序
    1. 就是上面的第三方jar包导入步骤
  3. JDBC版本
    1. JDBC 4.0包含在Java SE 6中
    2. JDBC 4.1包含在Java SE 7中
    3. JDBC 4.2包含在Java SE 8中
    4. JDBC 4.3包含在Java SE 9中
  4. 这里说的是JDBC的版本,是嵌入在JDK中的代码,不是mysql等其他厂商的数据库版本
  5. 即:从Java SE 7开始,就可以省略第一步注册Driver的代码了

Statement

Statement的常用API

  1. ResultSet executeQuery(String sql)
    1. 执行DQL语句
  2. int executeUpdate(String sql)
    1. 执行DML、DDL语句
    2. 如果是DML语句,返回值代表影响的记录数量;如果数据库没有任何返回值,通常返回0

ResultSet 的常用API

  1. boolean next()
    1. 让游标指向下一行。如果指向的这行有数据,就返回true,否则,返回false
  2. XX getXX(int columnIndex) 、XX getXX(String columnLabel)
    1. 获取当前行(游标指向的那行) 某一列的数据
    2. columnIndex的数值从1开始
    3. XX指的是数据类型,比如int、double…
  3. 代码举例

     public class Main {
         private static final String driverClassName = "com.mysql.jdbc.Driver";
         private static final String URL = "jdbc:mysql://localhost:3306/zh_info";
         private static final String USERNAME = "root";
         private static final String PASSWORD = "root";
        
        
         public static void main(String[] args) throws Exception {
             try(Connection connection =  DriverManager.getConnection(URL,USERNAME,PASSWORD);
                 Statement statement = connection.createStatement()
             ){
                 String sql = "SELECT * FROM student";
                 ResultSet rs = statement.executeQuery(sql);
                 rs.next();//第一条数据,让游标指向第一条记录
                 //获取第一条数据的age值
                 System.out.println(rs.getInt("age"));
                 System.out.println(rs.getString("name"));
                 //列的索引,对应的值,从1开始
                 System.out.println(rs.getInt(2));
             }
         }
     }
    

PreparedStatement

  1. PreparedStatement接口继承自Statement接口
  2. 建议使用PreparedStatement替代Statement
  3. PreparedStatement的优点
    1. 可以防止SQL注入
    2. 执行速度比Statement快
    3. 支持批量处理
  4. 代码举例:

     private static void login(String username,String password) throws Exception {
         try(Connection connection =  DriverManager.getConnection(URL,USERNAME,PASSWORD);
             Statement statement = connection.createStatement()
         ){
             String sql = "SELECT * FROM user WHERE username = '" + username+"' AND password = '"+ password + "'";
    
             //SELECT * FROM user WHERE username = 'xxxx' AND password = '' OR '1' = '1'
             System.out.println(sql);
    
             ResultSet rs = statement.executeQuery(sql);
             if (rs.next()){
                 System.out.println("登录成功");
             }else {
                 System.out.println("登录失败");
             }
         }
     }
        
     //main函数
     //SQL注入
     String username = "xxxx";//真正的账号是zhangsan
     String password = "' OR '1' = '1";//真正的密码是123
     login(username,password);
     //结果是:登录成功
    
    1. 这么一些,那么拼接后的sql语句为:SELECT * FROM user WHERE username = 'xxxx' AND password = '' OR '1' = '1',那么永远成立,因此怎么都登录成功
  5. 使用PreparedStatement

     private static void login2(String username,String password) throws Exception {
         String sql = "SELECT * FROM user WHERE username = ? AND password = ?";
         try(Connection connection =  DriverManager.getConnection(URL,USERNAME,PASSWORD);
             PreparedStatement pstat =  connection.prepareStatement(sql);
             ) {
             //设置上面的那两个?占位
             pstat.setString(1,username);
             pstat.setString(2,password);
             ResultSet rs = pstat.executeQuery();
             if (rs.next()){
                 System.out.println("登录成功");
             }else {
                 System.out.println("登录失败");
             }
         }
     }
     //结果是登录失败
    
    1. 这么写会直接让?占位符的内容进行字符串转义,并不是直接拼接,防止注入

JDBC的封装(重点!!!)

  1. 依然用第一节的案例用户列表维护来讲解
  2. 将mysql的jdbc驱动包导入到项目(javaEE)中

封装1

  1. 将这些字符串常量抽取到一个类中,成为类成员常量
  2. 2个servlet(ListServlet/SaveServlet)都有数据处理的代码:连接数据库、查询数据、添加数据,将数据库处理的代码封装到DAO(Data Access Object)中,专门用于处理数据的对象
    1. 在项目目录下建立一个与bean同级的文件夹dao文件夹,里面专门用于存放每个表对应的dao对象
  3. 封装一个常量Constants

     package com.zh.crm;
     public class Constants {
         public  static  final String DRIVERCLASSNAME = "com.mysql.jdbc.Driver";
         public  static  final String URL = "jdbc:mysql://localhost:3306/crm";
         public  static  final String USERNAME = "root";
         public  static  final String PASSWORD = "root";
     }
    
  4. CustomerDao类的封装

     public class CustomerDao {
         //保存用户信息
         public  boolean save(Customer customer){
             try {
                 String sql = "INSERT INTO customer(name, age, height) VALUES(?, ?, ?)";
                 Class.forName(Constants.DRIVERCLASSNAME);
                 try(Connection con =  DriverManager.getConnection(Constants.URL,Constants.USERNAME,Constants.PASSWORD);
                     PreparedStatement stam = con.prepareStatement(sql)){
                     stam.setString(1,customer.getName());
                     stam.setInt(2,customer.getAge());
                     stam.setDouble(3,customer.getHeight());
                     return stam.executeUpdate() > 0;
                 }
             }catch (Exception e){
                 e.printStackTrace();
                 return false;
             }
         }
         //获取用户信息
         public List<Customer> list(){
             try {
                 String sql = "SELECT id ,name, age, height FROM customer";
                 Class.forName(Constants.DRIVERCLASSNAME);
                 try(Connection con =  DriverManager.getConnection(Constants.URL,Constants.USERNAME,Constants.PASSWORD);
                     PreparedStatement stam = con.prepareStatement(sql);
                     ResultSet rs = stam.executeQuery()){
        
                     List<Customer> customers = new ArrayList<>();
                     while (rs.next()){
                         Customer customer = new Customer();
                         customer.setAge(rs.getInt("age"));
                         customer.setId(rs.getInt("id"));
                         customer.setName(rs.getString("name"));
                         customer.setHeight(rs.getDouble("height"));
                         customers.add(customer);
                     }
                     return  customers;
                 }
             }catch (Exception e){
                 e.printStackTrace();
                 return null;
             }
         }
     }
    
  5. 将ListServlet的dopost中代码改造如下:

     private final CustomerDao dao = new CustomerDao();
     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
         //1. 从数据库获取所有数据
         request.setAttribute("customers",dao.list());
         request.getRequestDispatcher("/page/list.jsp").forward(request,response);
     }
    
  6. 将SaveServlet中的dopost中代码改造如下:

     private final CustomerDao dao = new CustomerDao();
     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
         request.setCharacterEncoding("UTF-8");
         //获取客户端发送的参数
         Customer customer = new Customer();
         customer.setName(request.getParameter("name"));
         customer.setAge(Integer.parseInt(request.getParameter("age")));
         customer.setHeight(Double.parseDouble(request.getParameter("height")));
         if (dao.save(customer)){
             //重定向到list
             response.sendRedirect("/crm/list");
         }else {
             //重定向到失败
             request.setAttribute("error","保存用户信息失败");
             request.getRequestDispatcher("/page/error.jsp").forward(request,response);
         }
     }
    

封装2

  1. 当有多个dao类时,每个类都有list与save方法,每个dao类都连接同一个数据库,那么每个类都要写很多的重复代码,那么就可以抽出一个工具类,把相同的代码抽取出来
  2. 在项目目录下新建一个util文件夹与dao同级,然后新建一个类Dbs(在Java中结尾为s的类通常是工具类,比如Arrays)
  3. Dbs类:

     public class Dbs {
         //这几个常量可以写在这里面,Constants类就不需要了
         private  static  final String DRIVERCLASSNAME = "com.mysql.jdbc.Driver";
         private  static  final String URL = "jdbc:mysql://localhost:3306/crm";
         private  static  final String USERNAME = "root";
         private  static  final String PASSWORD = "root";
         /**
          * 执行DML、DDL语句
          * @return
          */
         public static int update(String sql,Object ...args){
             try {
                 Class.forName(DRIVERCLASSNAME);
                 try(Connection con =  DriverManager.getConnection(URL,USERNAME,PASSWORD);
                     PreparedStatement stam = con.prepareStatement(sql)){
                     //设置参数
                     for (int i = 0; i < args.length; i++) {
                         stam.setObject(i+1,args[i]);
                     }
                     return stam.executeUpdate();
                 }
             }catch (Exception e){
                 e.printStackTrace();
                 return 0;
             }
         }
        
         /**
          * 查询
          * @param sql
          * @param mapper
          * @param args 可变参数
          * @param <T>
          * @return
          */
         public static <T> List<T> query(String sql,RowMapper<T> mapper,Object ...args){
             if (mapper == null) return null;
             try {
                 Class.forName(DRIVERCLASSNAME);
                 try(Connection con =  DriverManager.getConnection(URL,USERNAME,PASSWORD);
                     PreparedStatement stam = con.prepareStatement(sql)){
                     //设置参数
                     for (int i = 0; i < args.length; i++) {
                         stam.setObject(i+1,args[i]);
                     }
                     ResultSet rs = stam.executeQuery();
                     List<T> array = new ArrayList<>();
                     int row = 0;
                     while (rs.next()){
                         //rs->bean,数据转模型
                         array.add(mapper.map(rs,row++));
                     }
                     rs.close();
                     return  array;
                 }
             }catch (Exception e){
                 e.printStackTrace();
                 return null;
             }
         }
        
         /**
          * 该接口用来执行每一行数据到bean的映射
          * @param <T>
          */
         public  interface RowMapper<T>{
             //throw:允许抛出异常
             T map(ResultSet ts,int row) throws Exception;
         }
     }
    
  4. CustomerDao类中的save、list方法

     //保存用户信息,只需要传sql语句,跟参数
     public  boolean save(Customer customer){
         String sql = "INSERT INTO customer(name, age, height) VALUES(?, ?, ?)";
         return Dbs.update(sql,customer.getName(),customer.getAge(),customer.getHeight()) > 0;
     }
     //获取用户信息
     public List<Customer> list(){
         String sql = "SELECT id ,name, age, height FROM customer";
         //lama表达式
         return Dbs.query(sql,(rs,row) -> {
             Customer customer = new Customer();
             customer.setAge(rs.getInt("age"));
             customer.setId(rs.getInt("id"));
             customer.setName(rs.getString("name"));
             customer.setHeight(rs.getDouble("height"));
             return customer;
         });
         //匿名对象
     // return Dbs.query(sql,new Dbs.RowMapper<Customer>() {
     //     @Override
     //     public Customer map(ResultSet rs, int row) throws Exception {
     //         Customer customer = new Customer();
     //         customer.setAge(rs.getInt("age"));
     //         customer.setId(rs.getInt("id"));
     //         customer.setName(rs.getString("name"));
     //         customer.setHeight(rs.getDouble("height"));
     //         return customer;
     //     }
     // });
     }
    
  5. 疑问?
    1. 按照上面的封装,如果某天数据库的密码修改了,那么就需要重新修改Dbs里面的字符串常量,然后重新打包发布到tomcat
    2. 那么有没有一种方法可以将常用的变量设置成动态的,不用再次发布就可以呢? —-配置文件

配置文件

  1. 一些经常动态修改的值, 建议放入到配置文件中, 不要写死在Java代码中
  2. Java中常见的配置文件
    1. properties:比较单一, 适合量小、简单的配置
    2. xml:比较灵活, 适合量大、复杂的配置
      1. 新建完项目web/WEB-INF目录下有个web.xml就是
  3. properties文件内容格式
    1. 用于存放键值对
    2. key、value的分隔符是=、:
    3. 建议分隔符左右不要留空格
    4. #、!开头是单行注释
    5. 可以用反斜线\连接多行内容:一行内容太多可以分行写

封装3

  1. 在上面项目的src文件夹下新建一个properties
    1. 右击src->new->resource bundle(资源包)->名称输入:db ->点击ok 会自动生成一个db.properties
    2. 注意:必须建立到src文件夹中,只有这样项目编译后的class文件夹的第一级子目录才会有这个db.properties文件
    3. 编译后的项目目录

       out
           artifacts
               04_crm
                   index.jsp
                   page(前端资源文件)
                   WEB-INF
                       classes
                           com(一级一级包名,最后是java的class字节码)
                           db.properties
                           ...
                       lib
                       web.xml(默认的)
      
    4. 可以看到db.properties资源文件与com中的java字节码同级
  2. db.properties的键值对如下:

     #zhushi
     url=jdbc:mysql://localhost:3306/crm
     username=root
     password=root
    
  3. Dbs工具类的代码如下:

     public class Dbs {
         private  static  String url;
         private  static  String username;
         private  static  String password;
         //类第一次加载的时候读取db.properties
         static {
            try(InputStream is = Dbs.class.getClassLoader().getResourceAsStream("db.properties")) {
                //Dbs.class.getClassLoader():是获取到Dbs类编译后的classes目录下,在classes目录下有一个com文件夹(存放java编译后的calss代码),还有一个db.properties文件,这样就可直接在该目录下获取db.properties文件的流数据了
                //InputStream is = Dbs.class.getClassLoader().getResourceAsStream("db.properties");
                //java提供的API
                Properties properties = new Properties();
                properties.load(is);
                url = properties.getProperty("url");
                username = properties.getProperty("username");
                password = properties.getProperty("password");
            }catch (Exception e){
                 e.printStackTrace();
            }
         }
            
         //下面update、query代码不变,只是这句变了
         ...
         Connection con =  DriverManager.getConnection(url,username,password);       
         ...
     }
    
  4. 如果用xml,同样在src文件夹下新建一个db.xml,然后在Dbs类中读取这个xml文件,然后解析内容。db.xml文件内容如下

     <?xml version="1.0" encoding="UTF-8"?>
     <db>
         <url>jdbc:mysql://localhost:3306/crm</url>
         <username>root</username>
         <password>root</password>
     </db>
    

怎么体现可以自动替换不用重新打包部署呢?

  1. 上面讲过db.properties编译后,会直接原样复制到classes文件夹下的第一级目录下
  2. 如果修改db.properties文件夹的内容,直接替换即可,不需要重新打包部署

封装4

  1. 每一个请求都对应一个servlet,比较繁琐,那么如何是多个请求对应一个servlet呢?
    1. 一个路径下对应多个子路径,一个servlet只监听这个主路径

       http://localhost:8081/crm/customer/list
       http://localhost:8081/crm/customer/save
       http://localhost:8081/crm/customer/remove
      
    2. 可以新建一个CustomerServlet,只监听http://localhost:8081/crm/customer

  2. 代码举例
    1. add.html中的action要修改为:/crm/customer/save
    2. CustomerServlet代码如下:

       @WebServlet("/customer/*")
       public class CustomerServlet extends HttpServlet {
           private final CustomerDao dao = new CustomerDao();
           @Override
           protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
               doPost(request,response);
           }
              
           @Override
           protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
               //反射
               //获取当前类中声明的所有方法,即使是private
               //Method[] methods = getClass().getDeclaredMethods();
               //获取当前类所有方法,包括父类的所有public方法
               //Method[] methods = getClass().getMethods();
               //获取成员变量
               //getClass().getFields();
               //getClass().getDeclaredFields();
               //获取方法:方法名、参数
               //Method method = getClass().getMethod(methodName,Integer.class,String.class);
               try {
                   String uri = request.getRequestURI();
                   String[] comps = uri.split("/");
                   String methodName = comps[comps.length - 1];
                   //System.out.println(Arrays.toString(comps));
                   //获取方法实例
                   Method method = getClass().getMethod(methodName,HttpServletRequest.class,HttpServletResponse.class);
                   //方法调用
                   method.invoke(this,request,response);
               } catch (Exception e) {
                   e.printStackTrace();
               }
           }
           public void save(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
               request.setCharacterEncoding("UTF-8");
               //获取客户端发送的参数
               Customer customer = new Customer();
               customer.setName(request.getParameter("name"));
               customer.setAge(Integer.parseInt(request.getParameter("age")));
               customer.setHeight(Double.parseDouble(request.getParameter("height")));
               if (dao.save(customer)){
                   //重定向到list
                   response.sendRedirect("/crm/customer/list");
               }else {
                   //重定向到失败
                   request.setAttribute("error","保存用户信息失败");
                   request.getRequestDispatcher("/page/error.jsp").forward(request,response);
               }
           }
           public void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
               //1. 从数据库获取所有数据
               request.setAttribute("customers",dao.list());
               request.getRequestDispatcher("/page/list.jsp").forward(request,response);
           }
           public void remove(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
              
           }
       }
      

数据库连接池

  1. 数据库连接池,可以提高访问数据库的性能,负责创建、分配、管理和释放数据库连接
    1. 上面的Dbs的代码中,每次调用update、query方法时都是从新连接数据库,然后访问数据库,访问完断开连接,每次都是这样过程,非常消耗性能
  2. 基本思想
    1. 创建:在初始化时,创建一定数量的数据库连接对象存储在内存中
    2. 分配:当需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已创建的空闲连接对象
    3. 管理:使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用
    4. 释放:当连接的空闲时间已超过最大空闲时间时,将会释放掉该连接
    5. 可以通过设置参数来控制初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间
    6. 可以通过其自身的管理机制来监视数据库连接的数量、使用情况
  3. Java中常见的开源的数据库连接池
    1. C3P0、Proxool、DBCP、BoneCP、Druid

Druid

  1. 产自阿里巴巴,项目地址:https://github.com/alibaba/druid/
  2. jar包下载:https://mvnrepository.com/artifact/com.alibaba/druid
    1. 专门下载jar包的网站(常用jar包下载地址
  3. 使用
    1. 下载druid-1.2.2.jar、druid-1.2.2-sources.jar(源码) 这2个包
    2. 将druid-1.2.2.jar导入lib中,然后加载到项目

封装5

  1. src下新增一个druid.properties文件

     driverClassName=com.mysql.jdbc.Driver
     url=jdbc:mysql://localhost:3306/crm
     username=root
     password=root
     initialSize=5
     maxActive=10
     maxWait=5000
    
  2. Dbs封装如下

     public class Dbs {
        
         private static  DataSource ds;
         //类第一次加载的时候读取db.properties
         static {
            try(InputStream is = Dbs.class.getClassLoader().getResourceAsStream("druid.properties")) {
                Properties properties = new Properties();
                properties.load(is);
                //创建数据库连接池
                ds = DruidDataSourceFactory.createDataSource(properties);
            }catch (Exception e){
                 e.printStackTrace();
            }
         }
        
         /**
          * 执行DML、DDL语句
          * @return
          */
         public static int update(String sql,Object ...args){
             try {
                 //注意:!!!这句话不能放到下面的try()中,如果放进去会自动调用close关闭连接,这不需要关闭,连接池自动管理
                 Connection con = ds.getConnection();
                 try(PreparedStatement stam = con.prepareStatement(sql)){
                     ...
                 }
             }catch (Exception e){
                 ...
             }
         }
        
         public static <T> List<T> query(String sql,RowMapper<T> mapper,Object ...args){
             if (mapper == null) return null;
             try {
                 Connection con = ds.getConnection();
                 try(PreparedStatement stam = con.prepareStatement(sql)){
                     ...
                 }
             }catch (Exception e){
                 ...
             }
         }
         ... 
     }
    

Spring JDBC

  1. Spring JDBC框架可以帮助开发者节省大量开发工作,自动去处理一些低级细节,比如
    1. 异常处理、打开和关闭资源(Connection、Statement、ResultSet)
  2. 需要的jar包(1依赖于2、3、4、5)
    1. spring-jdbc
    2. spring-beans
    3. spring-core
    4. spring-tx
    5. spring-jcl
  3. 可以到https://mvnrepository.com去下载

Spring JDBC 核心类:JdbcTemplate

  1. 构造方法
    1. public JdbcTemplate(DataSource dataSource)
  2. 执行DDL、DML语句
    1. int update(String sql,Object... args)
  3. 执行DQL语句
    1. <T> List<T> query(String sql, RowMapper<T> rowMapper, Object... args)
    2. <T> T queryForObject(String sql, RowMapper<T> rowMapper, Object... args)

封装6

  1. 下载Spring JDBC对应的5个包,导入到项目lib中
  2. JdbcTemplate可以替换原来Dbs中封装的update跟query方法,则改造后如下:

     public class Dbs {
         private static JdbcTemplate tpl;
         //类第一次加载的时候读取db.properties
         static {
            try(InputStream is = Dbs.class.getClassLoader().getResourceAsStream("druid.properties")) {
                Properties properties = new Properties();
                properties.load(is);
                //创建连接池
                DataSource ds = DruidDataSourceFactory.createDataSource(properties);
                //连接池与spring jdbc无缝结合,将连接池给spring jdbc
                tpl = new JdbcTemplate(ds);
            }catch (Exception e){
                 e.printStackTrace();
            }
         }
        
         public static JdbcTemplate getTpl() {
             return tpl;
         }
     }
    
  3. CustomerDao改造后如下:

     public class CustomerDao {
         //保存用户信息
         public  boolean save(Customer customer){
             String sql = "INSERT INTO customer(name, age, height) VALUES(?, ?, ?)";
             List<Object> args = buildArgs(customer);
             return Dbs.getTpl().update(sql,args.toArray()) >0;
     //        return Dbs.getTpl().update(sql,customer.getName(),customer.getAge(),customer.getHeight()) > 0;
         }
         //获取用户信息
         public List<Customer> list(){
             String sql = "SELECT id ,name, age, height FROM customer";
             //告诉BeanPropertyRowMapper对象,将rs映射成Customer对象,本质是利用反射
             return Dbs.getTpl().query(sql,new BeanPropertyRowMapper<>(Customer.class));
             //lama表达式
     //        return Dbs.getTpl().query(sql,(rs,row) -> {
     //            Customer customer = new Customer();
     //            customer.setAge(rs.getInt("age"));
     //            customer.setId(rs.getInt("id"));
     //            customer.setName(rs.getString("name"));
     //            customer.setHeight(rs.getDouble("height"));
     //            return customer;
     //        });
         }
        
         /**
          * 删除Customer
          * @param id
          * @return
          */
         public boolean remove(Integer id){
             String sql = "DELETE FROM customer WHERE id = ?";
             return Dbs.getTpl().update(sql,id)>0;
         }
         /**
          * 查询Customer
          * @param id
          * @return
          */
         public Customer find(Integer id){
             String sql = "SELECT id ,name, age, height FROM customer WHERE id = ?";
             //直接映射成Customer对象
             return Dbs.getTpl().queryForObject(sql,new BeanPropertyRowMapper<>(Customer.class),id);
         }
         /**
          * 更新Customer
          * @param customer
          * @return
          */
         public boolean update(Customer customer){
             String sql = "UPDATE customer SET name = ?, age = ?, height = ? WHERE id = ?";
             List<Object> args = buildArgs(customer);
             args.add(customer.getId());
             return Dbs.getTpl().update(sql,args.toArray()) >0;
     //        return Dbs.getTpl().update(sql,customer.getName(),customer.getAge(),customer.getHeight(),customer.getId()) > 0;
         }
         private List<Object> buildArgs(Customer customer){
             List<Object> args = new ArrayList<>();
             args.add(customer.getName());
             args.add(customer.getAge());
             args.add(customer.getHeight());
             return args;
         }
     }
    
  4. CustomerServlet改造如下

     @WebServlet("/customer/*")
     public class CustomerServlet extends HttpServlet {
         private final CustomerDao dao = new CustomerDao();
         @Override
         protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
             doPost(request,response);
         }
        
         @Override
         protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
             try {
                 request.setCharacterEncoding("UTF-8");
                 String uri = request.getRequestURI();
                 String[] comps = uri.split("/");
                 String methodName = comps[comps.length - 1];
                 //获取方法实例
                 Method method = getClass().getMethod(methodName,HttpServletRequest.class,HttpServletResponse.class);
                 //方法调用
                 method.invoke(this,request,response);
             } catch (Exception e) {
                 e.printStackTrace();
                 //重定向到失败
                 forwardError(request,response,"访问路径有问题");
             }
         }
         public void save(HttpServletRequest request, HttpServletResponse response) throws Exception{
        
             //获取客户端发送的参数
             Customer customer = newCustomer(request);
             if (dao.save(customer)){
                 //重定向到list
                 response.sendRedirect("/crm/customer/list");
             }else {
                 //重定向到失败
                 forwardError(request,response,"保存用户信息失败");
             }
         }
         public void list(HttpServletRequest request, HttpServletResponse response) throws Exception{
             //1. 从数据库获取所有数据
             request.setAttribute("customers",dao.list());
             request.getRequestDispatcher("/page/list.jsp").forward(request,response);
         }
         public void remove(HttpServletRequest request, HttpServletResponse response) throws Exception{
             Integer id = Integer.valueOf(request.getParameter("id"));
             if (dao.remove(id)){
                 //删除成功,刷新
                 response.sendRedirect("/crm/customer/list");
             }else {
                 //重定向到失败
                 forwardError(request,response,"删除用户信息失败");
             }
         }
         public void edit(HttpServletRequest request, HttpServletResponse response) throws Exception{
             Integer id = Integer.valueOf(request.getParameter("id"));
             Customer customer = dao.find(id);
             //转发给jsp
             request.setAttribute("customer",customer);
             request.getRequestDispatcher("/page/update.jsp").forward(request,response);
         }
         public void update(HttpServletRequest request, HttpServletResponse response) throws Exception{
             //获取客户端发送的参数
             Customer customer = newCustomer(request);
             customer.setId(Integer.valueOf(request.getParameter("id")));
             if (dao.update(customer)){
                 //删除成功,刷新
                 response.sendRedirect("/crm/customer/list");
             }else {
                 //重定向到失败
                 forwardError(request,response,"更新用户信息失败");
             }
         }
         private void forwardError(HttpServletRequest request, HttpServletResponse response,String error)throws ServletException, IOException{
             //重定向到失败
             request.setAttribute("error",error);
             request.getRequestDispatcher("/page/error.jsp").forward(request,response);
         }
        
         private Customer newCustomer(HttpServletRequest request) throws Exception{
             //这些可以用反射
             Customer customer = new Customer();
     //        customer.setName(request.getParameter("name"));
     //        customer.setAge(Integer.parseInt(request.getParameter("age")));
     //        customer.setHeight(Double.parseDouble(request.getParameter("height")));
             //使用apache封装好的轮子,专门用户数据转模型:beanutils
             /*
             导入这3个jar包
             commons-beanutils-1.9.4.jar
             commons-collections-3.2.2.jar
             commons-logging-1.2.jar
             * */
             BeanUtils.populate(customer,request.getParameterMap());
             return customer;
         }
     }
    
  5. 用户列表页面list.jsp新增编辑、删除功能

     //list.jsp
     //表头
     <tr>
         <th>姓名</th>
         <th>年龄</th>
         <th>身高</th>
         <th>操作</th>
     </tr>
        
     //每行内容
     <c:forEach items="${customers}" var="customer" varStatus="s">
         <tr>
             <td>${customer.name}</td>
             <td>${customer.age}</td>
             <td>${customer.height}</td>
             <td>
                 <a href="/crm/customer/edit?id=${customer.id}">编辑</a>
                 <a href="/crm/customer/remove?id=${customer.id}">删除</a>
             </td>
         </tr>
     </c:forEach>
    
  6. 新增编辑页面update.jsp

     <%@ page contentType="text/html;charset=UTF-8" language="java" %>
     <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
     <!DOCTYPE html>
     <html lang="zh">
     <head>
         <meta charset="UTF-8">
         <title>编辑客户</title>
     </head>
     <body>
        
     <form action="/crm/customer/update" method="post">
         <input type="hidden" name="id" value="${customer.id}">
         <div>姓名 <input type="text" name="name" value="${customer.name}"></div>
         <div>年龄 <input type="text" name="age" value="${customer.age}"></div>
         <div>身高 <input type="text" name="height" value="${customer.height}"></div>
         <div><button type="submit">更新</button></div>
     </form>
        
     </body>
     </html>
    

图1

Table of Contents