栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

jdbc学习笔记以及DAO层设计

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

jdbc学习笔记以及DAO层设计

1 简介和基础使用 1.1 简介

JDBC:是sun发布的一个java程序和数据库之间通信的规范(接口)

各大数据库厂商去实现JDBC规范(实现类),将这些实现类打成压缩包,就是所谓的jar包

比如:

1.2 创建连接

url:防止中文乱码,加上参数useUnicode=true&characterEncoding=utf-8。url的标准格式为:jdbc:mysql://ip:端口号/数据库名称?参数列表

public class Demo01 {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
//        1.添加jar包
//        2.加载驱动
       Class.forName("com.mysql.jdbc.Driver");
//       3.通过驱动管理器获取连接对象
//        3-1 准备url
        String url = "jdbc:mysql://localhost:3306/fruitdb";
//        3-2 准备用户名
        String usr = "root";
//        3-3 准备密码
        String pwd = "root";
        Connection connection = DriverManager.getConnection(url,usr,pwd);
        System.out.println("conn = "+connection);
    }
}
1.3 添加数据

先建立连接之后,得到connection对象,这个对象相当于建立了一条连接数据库和java之间的马路,通过connection对象得到预处理对象PreparedStatement,这个对象相当于马路上的一辆车,用于运送真实的数据。

在SQL语句中,使用?当做占位符,再通过预处理对象的set方法为占位符填充内容

最后,记得关闭连接,先关闭预处理对象,再关闭连接对象

public class Demo02 {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
//        1.加载驱动
        Class.forName("com.mysql.jdbc.Driver");
        //       2.通过驱动管理器获取连接对象
//        2-1 准备url
//        url表示和数据库通信的地址,如果需要带参数,则需要使用?进行连接
//        如果使用多个参数,多个参数之间使用&连接
        String url = "jdbc:mysql://localhost:3306/fruitdb?useSSL=false&useUnicode=true&characterEncoding=utf-8";
//        2-2 准备用户名
        String usr = "root";
//        2-3 准备密码
        String pwd = "root";
        
        Connection connection = DriverManager.getConnection(url,usr,pwd);

//        3. 编写SQL语句
//        id,fname,price,fcount,remark
        String sql = "insert into t_fruit value(0,?,?,?,?)";

//        4.创建预处理对象
        PreparedStatement psmt = connection.prepareStatement(sql);
//        5.填充参数
        psmt.setString(1,"榴莲");
        psmt.setInt(2,15);
        psmt.setInt(3,100);
        psmt.setString(4,"榴莲是一种神奇的水果");
//        6.执行更新(增删改),返回影响行数
        int i = psmt.executeUpdate();
        System.out.println(i>0?"添加成功!":"添加失败");
//        7.释放资源
        psmt.close();
        connection.close();
    }
}
1.4 更新和删除数据

和添加数据步骤都一样,最后都是预处理对象调用executeUpdate方法并返回影响行数。因此更新与修改和添加的不同就是SQL语句和预处理设置SQL中的占位符的值而已。

更新:

 Fruit fruit = new Fruit(33, "猕猴桃", "猕猴桃啊猕猴桃");

        Class.forName("com.mysql.jdbc.Driver");
        String url = "jdbc:mysql://localhost:3306/fruitdb?useSSL=false&useUnicode=true&characterEncoding=utf8";
        Connection connection = DriverManager.getConnection(url, "root", "root");

        String sql = "update t_fruit set fname=?,remark = ? where fid = ?";
        PreparedStatement psmt = connection.prepareStatement(sql);
        psmt.setString(1, fruit.getFname());
        psmt.setString(2, fruit.getRemark());
        psmt.setInt(3,fruit.getFid());
        int i = psmt.executeUpdate();
        System.out.println(i>0?"修改成功":"修改失败");

        psmt.close();
        connection.close();

删除:

  Class.forName("com.mysql.jdbc.Driver");
        String url = "jdbc:mysql://localhost:3306/fruitdb?useSSL=false&useUnicode=true&characterEncoding=utf8";
        Connection connection = DriverManager.getConnection(url, "root", "root");

        String sql = "delete from t_fruit where fid = ?";
        PreparedStatement psmt = connection.prepareStatement(sql);
        psmt.setInt(1,33);

        int i = psmt.executeUpdate();
        System.out.println(i>0?"删除成功":"删除失败");

        psmt.close();
        connection.close();
1.5 增删更新总结 1.5.1 步骤:
  1. 加载驱动
  2. 通过驱动获取连接对象Connection,连接对象相当于连接java与数据库之间的一条马路
  3. 编写SQL语句,语句中使用占位符
  4. 创建数据预处理对象PreparedStatement,数据预处理对象相当于马路上运送SQL语句的小车
  5. 填充预处理数据对象的参数
  6. 执行更新参数,对于增删查都是执行PreparedStatement对象的executeUpdate方法,返回影响行数
  7. 关闭资源,关闭PreparedStatement对象和Connection对象
1.6 查询

查询与增删改的不同之处在于:

  • 预处理对象执行的是executeQuery方法
  • 方法返回结果集ResultSet,可以遍历结果接进行数据的操作

例如:查询所有数据

//        1.加载驱动
        Class.forName("com.mysql.jdbc.Driver");
        String url = "jdbc:mysql://localhost:3306/fruitdb?useSSL=false&useUnicode=true&characterEncoding=utf8";
//        2.获取连接对象
        Connection connection = DriverManager.getConnection(url, "root", "root");

//        3.编写SQL语句
        String sql = "select * from t_fruit";
//        4.创建预处理命令对象
        PreparedStatement psmt = connection.prepareStatement(sql);
//        5.填充参数,这里没有,略
//        6.执行查询,返回结果集
        ResultSet res = psmt.executeQuery();
        List list = new ArrayList<>();
//        7.处理结果集
//        判断下一行是否有数据,并且指针指到下一行
        while(res.next()){
//            表示当前行第一列的数据,因为这一列是int类型的数据,所以使用getInt
            int fid = res.getInt(1);
//            也可以通过结果集的列名
            String fname = res.getString("fname");
            int price = res.getInt(3);
            int fcount = res.getInt("fcount");
            String remark = res.getString("remark");

            Fruit fruit = new Fruit(fid,fname,price,fcount,remark);
            list.add(fruit);
        }
//        8.释放资源
        res.close();
        psmt.close();
        connection.close();
        for (Fruit fruit : list) {
            System.out.println(fruit);
        }

查询指定的一条记录:

		Class.forName("com.mysql.jdbc.Driver");
        String url = "jdbc:mysql://localhost:3306/fruitdb?useSSL=false&useUnicode=true&characterEncoding=utf8";
        Connection connection = DriverManager.getConnection(url, "root", "root");

//        编写SQL语句
        String sql = "select * from t_fruit where fid = ?";
//        创建预处理命令对象
        PreparedStatement psmt = connection.prepareStatement(sql);
//        填充参数
        psmt.setInt(1,2);
//        由于fid是主键,所以可以不使用列表,这里改一下
//        执行查询,返回结果集
        ResultSet res = psmt.executeQuery();
//        判断下一行是否有数据,并且指针指到下一行
        if(res.next()){
//            表示当前行第一列的数据,因为这一列是int类型的数据,所以使用getInt
            int fid = res.getInt(1);
//            也可以通过结果集的列名
            String fname = res.getString("fname");
            int price = res.getInt(3);
            int fcount = res.getInt("fcount");
            String remark = res.getString("remark");
            Fruit fruit = new Fruit(fid,fname,price,fcount,remark);
            System.out.println(fruit);
        }
//        释放资源
        res.close();
        psmt.close();
        connection.close();

查询总记录条数:

        Class.forName("com.mysql.jdbc.Driver");
        String url = "jdbc:mysql://localhost:3306/fruitdb?useSSL=false&useUnicode=true&characterEncoding=utf8";
        Connection connection = DriverManager.getConnection(url, "root", "root");

//        编写SQL语句
        String sql = "select count(*) from t_fruit";
//        创建预处理命令对象
        PreparedStatement psmt = connection.prepareStatement(sql);
//        执行查询,返回结果集
//        这里返回的是一个一行一列的数据
        ResultSet res = psmt.executeQuery();
//        判断下一行是否有数据,并且指针指到下一行
        if(res.next()){
            int count = res.getInt(1);
            System.out.println("总记录条数:"+count);
        }
//        释放资源
        res.close();
        psmt.close();
        connection.close();
1.7 查询总结 1.7.1 查询与增删改的不同之处在于:
  • 预处理对象执行的是executeQuery方法
  • 方法返回结果集ResultSet,可以遍历结果接进行数据的操作
1.7.2 步骤:
  1. 加载驱动
  2. 通过驱动获取连接对象Connection,连接对象相当于连接java与数据库之间的一条马路
  3. 编写SQL语句,语句中使用占位符
  4. 创建数据预处理对象PreparedStatement,数据预处理对象相当于马路上运送SQL语句的小车
  5. 填充预处理数据对象的参数
  6. 执行更新参数,执行executeQuery方法
  7. 返回结果集ResultSet
  8. 关闭资源,关闭ResultSet对象、PreparedStatement对象和Connection对象
1.8 批处理

如果一次性添加大量数据,若每一条数据都单独执行一次方法,那么效率很低下,所以可以采用批处理的方法来提高处理的效率。

批处理操作的步骤:

  1. 如果执行批处理任务,需要在添加一个参数: rewriteBatchedStatements=true
  2. 将数据加入一个批次:psmt.addBatch();
  3. 执行批处理:int[] batch = psmt.executeBatch();如果继续进行处理,需要进行clear:psmt.clear();
package com.zylai.jdbc01;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Arrays;


public class DemoAddBatch {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
//        1.加载驱动
        Class.forName("com.mysql.jdbc.Driver");
        //       2.通过驱动管理器获取连接对象
//        批处理操作一
//        如果执行批处理任务,需要添加一个参数: rewriteBatchedStatements=true
        String url = "jdbc:mysql://localhost:3306/fruitdb?rewriteBatchedStatements=true&useSSL=false&useUnicode=true&characterEncoding=utf-8";
//        2-2 准备用户名
        String usr = "root";
//        2-3 准备密码
        String pwd = "root";
        Connection connection = DriverManager.getConnection(url,usr,pwd);
//        3. 编写SQL语句
        String sql = "insert into t_fruit value(0,?,?,?,?)";

//        4.创建预处理对象
        PreparedStatement psmt = connection.prepareStatement(sql);
//        5.填充参数
        for (int i = 0; i < 1000; i++) {
            psmt.setString(1,"榴莲"+i);
            psmt.setInt(2,15);
            psmt.setInt(3,100);
            psmt.setString(4,"榴莲是一种神奇的水果"+i);

//            批处理操作二:将数据加入一个批次
            psmt.addBatch();
//            每一百个处理一次,分批次处理,每次执行完要记得清空一下队列
            if(i%100==0){
                psmt.executeBatch();
//                记得clear一下
                psmt.clearBatch();
            }
        }
//        批处理操作三
        int[] batch = psmt.executeBatch();
        System.out.println(Arrays.toString(batch));

//        7.释放资源
        psmt.close();
        connection.close();
    }
}

2 工程 2.1 需求概述和整体框架

以实现一个水果库存系统为例,这个系统需要提供水果的增删改查操作

=================欢迎使用水果库存系统=====================
1.查看水果库存列表
2.添加水果库存信息
3.查看特定水果库存信息
4.水果下架
5.退出
======================================================

整体框架如下图,在controller里面写调用DAO层的方法,在dao里面写持久层方法,提供接口及其实现类,pojo写实体类,view是客户端视图操作。

2.2 最初版本 2.2.1 pojo

pojo包下的Fruit类

package com.zylai.fruit01.pojo;


public class Fruit {
    private Integer fid;
    private String fname;
    private Integer price;
    private Integer fcount;
    private String remark;

    public Fruit(){}

    public Fruit(Integer fid, String fname, String remark) {
        this.fid = fid;
        this.fname = fname;
        this.remark = remark;
    }

    public Fruit(Integer fid, String fname, Integer price, Integer fcount, String remark) {
        this.fid = fid;
        this.fname = fname;
        this.price = price;
        this.fcount = fcount;
        this.remark = remark;
    }

    @Override
    public String toString() {
        return fid+"tt"+fname+"tt"+price+"tt"+fcount+"tt"+remark;
    }

    public Integer getFid() {
        return fid;
    }

    public void setFid(Integer fid) {
        this.fid = fid;
    }

    public String getFname() {
        return fname;
    }

    public void setFname(String fname) {
        this.fname = fname;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    public Integer getFcount() {
        return fcount;
    }

    public void setFcount(Integer fcount) {
        this.fcount = fcount;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }
}

2.2.2 dao

FruitDAO接口

package com.zylai.fruit01.dao;

import com.zylai.fruit01.pojo.Fruit;

import java.util.List;


public interface FruitDAO {
//    查询库存列表
    List getFruitList();

//    根据水果名称查询库存
    Fruit getFruitByName(String fname);
//    新增水果库存
    boolean addFruit(Fruit fruit);
//    修改库存
    boolean updateFruit(Fruit fruit);
//    根据水果名称删除记录
    boolean deleteFruitByName(String fname);

}

FruitDAOImpl实现类

注意到这个实现类存在着大量的代码冗余,之后的优化主要对这个类进行优化

package com.zylai.fruit01.dao.impl;

import com.zylai.fruit01.dao.FruitDAO;
import com.zylai.fruit01.pojo.Fruit;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;


public class FruitDAOImpl implements FruitDAO {
    private final String DRIVER = "com.mysql.jdbc.Driver";
    private final String URL = "jdbc:mysql://localhost:3306/fruitdb?useUnicode=true&characterEncoding=utf-8&useSSL=false";
    private final String USER = "root";
    private final String PWD = "root";

//    查询所有的库存信息
    @Override
    public List getFruitList() {
        List fruitList = new ArrayList<>();
        Connection connection = null;
        PreparedStatement psmt = null;
        ResultSet rs = null;
        try {
//            1.加载驱动
            Class.forName(DRIVER);
//            2.通过驱动获取连接对象
             connection= DriverManager.getConnection(URL, USER, PWD);
//            3.编写SQL语句
            String sql = "select * from t_fruit";
//            4.创建预处理命令对象
             psmt = connection.prepareStatement(sql);
//            5.执行查询
             rs = psmt.executeQuery();
//            6.解析查询
            while(rs.next()){
                int fid = rs.getInt(1);
                String fname = rs.getString(2);
                int price = rs.getInt(3);
                int fcount = rs.getInt(4);
                String remark = rs.getString(5);

                Fruit fruit = new Fruit(fid,fname,price,fcount,remark);
                fruitList.add(fruit);
            }
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }finally {
            try {
                if(rs!=null){
                    rs.close();
                }
                if(psmt!=null){
                    psmt.close();
                }
                if(connection!=null){
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return fruitList;
    }

//    通过水果名称查询记录
    @Override
    public Fruit getFruitByName(String fname) {
        Connection connection = null;
        PreparedStatement psmt = null;
        ResultSet rs = null;
        try {
            Class.forName(DRIVER);
            connection = DriverManager.getConnection(URL,USER,PWD);

            String sql = "select * from t_fruit where fname=?";
            psmt = connection.prepareStatement(sql);
            psmt.setString(1,fname);
            rs= psmt.executeQuery();
            if(rs.next()){
                int fid = rs.getInt("fid");
                int price = rs.getInt("price");
                int fcount = rs.getInt("fcount");
                String remark = rs.getString("remark");

                return new Fruit(fid,fname,price,fcount,remark);
            }

        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }finally {
            try{
                if(rs!=null){
                    rs.close();
                }
                if(psmt!=null){
                    psmt.close();
                }
                if(connection!=null){
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

//    添加水果
    @Override
    public boolean addFruit(Fruit fruit) {
        Connection connection = null;
        PreparedStatement psmt = null;
        try {
            Class.forName(DRIVER);
            connection = DriverManager.getConnection(URL,USER,PWD);
            String sql = "insert into t_fruit values(0,?,?,?,?)";
            psmt = connection.prepareStatement(sql);
            psmt.setString(1,fruit.getFname());
            psmt.setInt(2,fruit.getPrice());
            psmt.setInt(3,fruit.getFcount());
            psmt.setString(4,fruit.getRemark());

            int i = psmt.executeUpdate();
            return i>0;
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }finally {
            try {
                if(psmt!=null){
                    psmt.close();
                }
                if(connection!=null){
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

//    更新水果库存信息
    @Override
    public boolean updateFruit(Fruit fruit) {
        Connection connection = null;
        PreparedStatement psmt = null;
        try {
            Class.forName(DRIVER);
            connection = DriverManager.getConnection(URL,USER,PWD);
            String sql = "update t_fruit set fname=?,price=?,fcount=?,remark=? where fid=?";
            psmt = connection.prepareStatement(sql);
            psmt.setString(1, fruit.getFname());
            psmt.setInt(2,fruit.getPrice());
            psmt.setInt(3,fruit.getFcount());
            psmt.setString(4,fruit.getRemark());
            psmt.setInt(5,fruit.getFid());

            int i = psmt.executeUpdate();
            return i>0;
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }finally {
            try {
                if(psmt!=null){
                    psmt.close();
                }
                if(connection!=null){
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

//    删除水果
    @Override
    public boolean deleteFruitByName(String fname) {
        Connection connection = null;
        PreparedStatement psmt = null;

        try {
            Class.forName(DRIVER);
            connection = DriverManager.getConnection(URL,USER,PWD);
            String sql = "delete from t_fruit where fname=?";
            psmt = connection.prepareStatement(sql);
            psmt.setString(1,fname);
            return psmt.executeUpdate()>0;
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }finally {
            try {
                if(psmt!=null){
                    psmt.close();
                }
                if(connection!=null){
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return false;
    }
}

2.2.3 controller

处理请求层,由于业务比较简单,所以一些业务都在controller里面进行

package com.zylai.fruit01.controller;

import com.zylai.fruit01.dao.FruitDAO;
import com.zylai.fruit01.dao.impl.FruitDAOImpl;
import com.zylai.fruit01.pojo.Fruit;

import java.util.List;
import java.util.Scanner;


public class Menu {

    Scanner input = new Scanner(System.in);
    private FruitDAO fruitDAO = new FruitDAOImpl();

    public int showMainMenu(){
        System.out.println("=================欢迎使用水果库存系统=====================");
        System.out.println("1.查看水果库存列表");
        System.out.println("2.添加水果库存信息");
        System.out.println("3.查看特定水果库存信息");
        System.out.println("4.水果下架");
        System.out.println("5.退出");
        System.out.println("======================================================");
        System.out.print("请选择:");
        int res = input.nextInt();
        while(true){
            if(res<1||res>5){
                System.out.println("请在1~5当中进行选择!");
                System.out.print("请选择:");
                res = input.nextInt();
            }else{
                break;
            }
        }
        return res;
    }
//    显示所有水果库存
    public void showFruitList(){
        List fruitList = fruitDAO.getFruitList();
        System.out.println("------------------------------------------------------");
        System.out.println("编号tt名称tt单价tt库存tt备注tt");
        if(fruitList==null||fruitList.size()<=0){
            System.out.println("对不起,库存为空");
        }else{
            for (Fruit fruit : fruitList) {
                System.out.println(fruit);
            }
        }
        System.out.println("------------------------------------------------------");
    }

//    添加水果库存信息 -- 业务方法
    public void addFruit(){
        System.out.print("请输入水果名称:");
        String fname = input.next();
//       判断是添加库存还是添加一个新水果
        Fruit fruitByName = fruitDAO.getFruitByName(fname);
        if(fruitByName==null){//说明库存中没有这个水果,直接添加
            System.out.print("请输入水果单价:");
            int price = input.nextInt();
            System.out.print("请输入水果库存量:");
            int fcount = input.nextInt();
            System.out.print("请输入水果备注:");
            String remark = input.next();
            fruitByName = new Fruit(0,fname,price,fcount,remark);
//            调用dao
            fruitDAO.addFruit(fruitByName);
        }else{
            System.out.print("请输入追加的库存量:");
            int fcount = input.nextInt();
            fruitByName.setFcount(fruitByName.getFcount()+fcount);
            fruitDAO.updateFruit(fruitByName);
        }
    }
//    查看特定水果库存信息
    public void showFruitInfo(){
        System.out.print("请输入水果名称:");
        String fname = input.next();
        Fruit fruit = fruitDAO.getFruitByName(fname);
        if(fruit == null){
            System.out.println("对不起,没有找到指定的水果库存记录");
        }else{
            System.out.println("------------------------------------------------------");
            System.out.println("编号tt名称tt单价tt库存tt备注tt");
            System.out.println(fruit);
            System.out.println("------------------------------------------------------");
        }
    }

    public void deleteFruit(){
        System.out.print("请输入水果名称:");
        String fname = input.next();
        Fruit fruit = fruitDAO.getFruitByName(fname);
        if(fruit==null){
            System.out.println("对不起,没有找到要下架的水果信息");
        }else{
            System.out.print("是否下架?(Y/N)");
            String str = input.next();
            if("y".equalsIgnoreCase(str)){
                boolean b = fruitDAO.deleteFruitByName(fname);
                if(b){
                    System.out.println("下架成功!");
                }
            }
        }
    }
//    退出
    public boolean exit(){
        do{
            System.out.print("是否确认退出?(Y/N):");
            String res = input.next();
            if("y".equalsIgnoreCase(res)){
                return true;
            }else if("n".equalsIgnoreCase(res)){
                return false;
            }
        }while(true);

    }
}

2.2.4 view
package com.zylai.fruit01.view;

import com.zylai.fruit01.controller.Menu;


public class Client {
    public static void main(String[] args) {
        Menu menu = new Menu();
        boolean flag = false;
        while(!flag){
            //        调用主菜单的方法
            int slt = menu.showMainMenu();
            switch(slt){
                case 1:
                    menu.showFruitList();
                    break;
                case 2:
                    menu.addFruit();
                    break;
                case 3:
                    menu.showFruitInfo();
                    break;
                case 4:
                    menu.deleteFruit();
                    break;
                case 5:
                    flag = menu.exit();
                    break;
            }
        }
        System.out.println("谢谢使用");
    }
}

2.2.5 最初版总结

代码的冗余量很大,主要是dao层的实现类,每个方法都要重复建立驱动、获取连接、关闭资源等操作。

2.3 改进:对于FruitDAOImpl中获取连接操作以及释放资源操作做了提取

这次的改进就是对于FruitDAOImpl中获取连接操作以及释放资源操作做了提取

如下:将加载驱动获取连接封装在getConnection方法中,将关闭资源封装在close方法中

    private Connection getConnection(){
        try {
            //       1.加载驱动
            Class.forName(DRIVER);
            //            2.通过驱动获取连接对象
            return DriverManager.getConnection(URL, USER, PWD);
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    private void close(Connection connection,PreparedStatement psmt, ResultSet rs){
        try {
            if(rs!=null){
                rs.close();
            }
            if(psmt!=null){
                psmt.close();
            }
            if(connection!=null){
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

//实际中使用
//    通过水果名称查询记录
    @Override
    public Fruit getFruitByName(String fname) {
        Connection connection = null;
        PreparedStatement psmt = null;
        ResultSet rs = null;
        try {

            connection = getConnection();

            String sql = "select * from t_fruit where fname=?";
            psmt = connection.prepareStatement(sql);
            psmt.setString(1,fname);
            rs= psmt.executeQuery();
            if(rs.next()){
                int fid = rs.getInt("fid");
                int price = rs.getInt("price");
                int fcount = rs.getInt("fcount");
                String remark = rs.getString("remark");

                return new Fruit(fid,fname,price,fcount,remark);
            }

        } catch ( SQLException e) {
            e.printStackTrace();
        }finally {
            close(connection,psmt,rs);
        }
        return null;
    }
2.4 改进:设置抽象类,并抽取增删改操作

可以看到下面这个三个操作的步骤完全一样,不一样的地方只是SQL语句和psmt设置的参数
因此可以将这些相同的操作抽取出来

  • addFruit:
    1.获取连接
    2.编写SQL
    3.psmt
    4.执行更新
    5.关闭资源

  • updateFruit:
    1.获取连接
    2.编写SQL
    3.psmt
    4.执行更新
    5.关闭资源

  • deleteFruit
    1.获取连接
    2.编写SQL
    3.psmt
    4.执行更新
    5.关闭连接

此外,这里只是涉及到一个表的操作,当有多个表时,就涉及到多个dao类,而这多个dao类都需要这共同的驱动地址,数据库url,用户名,密码,获取驱动方法,关闭资源的方法,抽取增删改操作方法。

因此,加入一个抽象类baseDAO,里面提供公共的常量和方法,并且把抽取的增删改方法加入

package com.zylai.fruit.dao.base;

import java.sql.*;


public abstract class BaseDAO {
    protected final String DRIVER = "com.mysql.jdbc.Driver";
    protected final String URL = "jdbc:mysql://localhost:3306/fruitdb?useUnicode=true&characterEncoding=utf-8&useSSL=false";
    protected final String USER = "root";
    protected final String PWD = "root";

    protected Connection getConnection(){
        try {
            //       1.加载驱动
            Class.forName(DRIVER);
            //            2.通过驱动获取连接对象
            return DriverManager.getConnection(URL, USER, PWD);
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

//    关闭资源
    protected  void close(Connection connection, PreparedStatement psmt, ResultSet rs){
        try {
            if(rs!=null){
                rs.close();
            }
            if(psmt!=null){
                psmt.close();
            }
            if(connection!=null){
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

//    执行更新,返回影响行数   
    protected int executeUpdate(String sql,Object... params){
        Connection connection = null;
        PreparedStatement psmt = null;
        try {
            connection = getConnection();
            psmt = connection.prepareStatement(sql);
            if(params!=null && params.length>0){
                for (int i = 0; i < params.length; i++) {
                    psmt.setObject(i+1,params[i]);
                }
            }
            return psmt.executeUpdate();
        } catch ( SQLException e) {
            e.printStackTrace();
        }finally {
            close(connection,psmt,null);
        }
        return 0;
    }

}

然后让daoImpl继承该抽象类

由于没有涉及到查询的改进,这里省去查询的方法。可以看到对于增删改方法,只需要调用父类的更新执行方法,传入sql语句和参数即可完成业务。

package com.zylai.fruit.dao.impl;

import com.zylai.fruit.dao.FruitDAO;
import com.zylai.fruit.dao.base.BaseDAO;
import com.zylai.fruit.pojo.Fruit;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;


public class FruitDAOImpl extends BaseDAO implements FruitDAO {

//    添加水果
    @Override
    public boolean addFruit(Fruit fruit) {
        String sql = "insert into t_fruit values(0,?,?,?,?)";
        return super.executeUpdate(sql,fruit.getFname(),fruit.getPrice(),fruit.getFcount(),fruit.getRemark())>0;
    }

//    更新水果库存信息
    @Override
    public boolean updateFruit(Fruit fruit) {
        String sql = "update t_fruit set fname=?,price=?,fcount=?,remark=? where fid=?";
        return super.executeUpdate(sql,fruit.getFname(),fruit.getPrice(),fruit.getFcount(),fruit.getRemark(),fruit.getFid())>0;
    }

//    删除水果
    @Override
    public boolean deleteFruitByName(String fname) {
        String sql = "delete from t_fruit where fname=?";
        return super.executeUpdate(sql,fname)>0;
    }
}

2.5 改进:抽取查询操作 2.5.1 使用泛型

通过执行查询操作返回查询的结果集,然后通过get方法得到指定列的值赋值给实体类比如Fruit,而对于baseDAO会有多个DAOImpl继承它,因此不能把实体类写死,所以采用泛型。

public abstract class BaseDAO {}

public class FruitDAOImpl extends BaseDAO implements FruitDAO {}
2.5.2 通过反射得到真正的类

因为泛型只是一个符号,我们不能直接通过泛型创建实例,所以用到反射技术来确定真正的实体类。

首先在类中定义Class 属性

//    T的Class对象
    private Class entityClass;

在BaseDAO的无参构造器中,确定真正的实体类

    public BaseDAO(){
//        getClass获取Class对象,当前我们执行的是new FruitDAOImpl(),创建的是FruitDAOImpl实例
//        那么构造方法首先会调用父类(BaseDAO)的无参构造方法
//        因此此处的getClass方法执行,获取的是FruitDAOImpl的Class
//        所以使用getGenericSuperclass
//        getGenericSuperclass是获取父类的泛型,即获取BaseDAO的泛型
        Type genericSuperclass = getClass().getGenericSuperclass();
//         ParameterizedType:参数化类型
//        获取(注意,泛型可以传递多个如,所以返回值是一个数组)中的T的真是类型
        Type[] actualTypeArguments = ((ParameterizedType) genericSuperclass).getActualTypeArguments();
        Type actualType = actualTypeArguments[0];

        try {
            entityClass = Class.forName(actualType.getTypeName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
2.5.3 查询方法

在处理结果集时我们不知道T到底有几个参数,是什么类型。因此,我们需要获取结果集的元数据,所谓元数据就是描述结果集数据的数据,简单来讲,就是这个结果集有哪些列,什么类型等等。

之后通过Class对象创建一个T的实例进行操作即可

先定义一个工具函数:

//    通过反射,给obj对象的property属性赋propertyValue值
    private void setValue(Object obj,String property,Object propertyValue){
        Class clazz = obj.getClass();
//        获取property这个字符串对应的属性名,比如“fid”去找obj中的fid属性
        try {
            Field field  = clazz.getDeclaredField(property);
            if(field!=null){
                //            强制访问
                field.setAccessible(true);
                //为obj对象设置filed属性的值
                field.set(obj,propertyValue);
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

执行查询的函数:

//    执行查询
    protected List executeQuery(String sql,Object... params){
        List list = new ArrayList<>();
        try {
//            获取连接
            connection = getConnection();
//            设置预处理对象
            psmt = connection.prepareStatement(sql);
//            设置参数
            setParams(psmt,params);
//            执行查询
            rs= psmt.executeQuery();
//            处理结果集
//            难点在于如何处理T,我们不知道T是什么类型并且不知道T有多少个参数
//            通过rs可以获取结果集的元数据
//            元数据:描述结果集数据的数据,简单讲,就是这个结果接有哪些列,什么类型等等
            ResultSetMetaData metaData = rs.getMetaData();
            int columnCount = metaData.getColumnCount();
            while(rs.next()){
                T entity = (T)entityClass.newInstance();
                for (int i = 0; i < columnCount; i++) {
//               获取列名
                    String columnName = metaData.getColumnName(i + 1);
//                获取列对应的值
                    Object columnValue = rs.getObject(i + 1);
//                    这里获取了列名,就需要根据列名把值填充给entity对象
                    setValue(entity,columnName,columnValue);
                }
                list.add(entity);

            }
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } finally {
            close(connection,psmt,rs);
        }

        return list;
    }

2.5.4 改进后的整个BaseDAO类为:
package com.zylai.fruit.dao.base;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;


public abstract class BaseDAO {
    protected final String DRIVER = "com.mysql.jdbc.Driver";
    protected final String URL = "jdbc:mysql://localhost:3306/fruitdb?useUnicode=true&characterEncoding=utf-8&useSSL=false";
    protected final String USER = "root";
    protected final String PWD = "root";

//    老师是吧这几个放到这里的,我觉得可能会出现并发问题,不过目前不考虑这个
    protected Connection connection = null;
    protected PreparedStatement psmt = null;
    protected ResultSet rs = null;

//    T的Class对象
    private Class entityClass;

    public BaseDAO(){
//        getClass获取Class对象,当前我们执行的是new FruitDAOImpl(),创建的是FruitDAOImpl实例
//        那么构造方法首先会调用父类(BaseDAO)的无参构造方法
//        因此此处的getClass方法执行,获取的是FruitDAOImpl的Class
//        所以使用getGenericSuperclass
//        getGenericSuperclass是获取父类的泛型,即获取BaseDAO的泛型
        Type genericSuperclass = getClass().getGenericSuperclass();
//         ParameterizedType:参数化类型
//        获取(注意,泛型可以传递多个如,所以返回值是一个数组)中的T的真是类型
        Type[] actualTypeArguments = ((ParameterizedType) genericSuperclass).getActualTypeArguments();
        Type actualType = actualTypeArguments[0];

        try {
            entityClass = Class.forName(actualType.getTypeName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    protected Connection getConnection(){
        try {
            //       1.加载驱动
            Class.forName(DRIVER);
            //            2.通过驱动获取连接对象
            return DriverManager.getConnection(URL, USER, PWD);
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

//    关闭资源
    protected  void close(Connection connection, PreparedStatement psmt, ResultSet rs){
        try {
            if(rs!=null){
                rs.close();
            }
            if(psmt!=null){
                psmt.close();
            }
            if(connection!=null){
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
//    给预处理对象设置参数
    private void setParams(PreparedStatement psmt,Object... params) throws SQLException {
        if(params!=null && params.length>0){
            for (int i = 0; i < params.length; i++) {
                psmt.setObject(i+1,params[i]);
            }
        }
    }


//    通过反射,给obj对象的property属性赋propertyValue值
    private void setValue(Object obj,String property,Object propertyValue){
        Class clazz = obj.getClass();
//        获取property这个字符串对应的属性名,比如“fid”去找obj中的fid属性
        try {
            Field field  = clazz.getDeclaredField(property);
            if(field!=null){
                //            强制访问
                field.setAccessible(true);
                //为obj对象设置filed属性的值
                field.set(obj,propertyValue);
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

//    执行查询
    protected List executeQuery(String sql,Object... params){
        List list = new ArrayList<>();
        try {
//            获取连接
            connection = getConnection();
//            设置预处理对象
            psmt = connection.prepareStatement(sql);
//            设置参数
            setParams(psmt,params);
//            执行查询
            rs= psmt.executeQuery();
//            处理结果集
//            难点在于如何处理T,我们不知道T是什么类型并且不知道T有多少个参数
//            通过rs可以获取结果集的元数据
//            元数据:描述结果集数据的数据,简单讲,就是这个结果接有哪些列,什么类型等等
            ResultSetMetaData metaData = rs.getMetaData();
            int columnCount = metaData.getColumnCount();
            while(rs.next()){
                T entity = (T)entityClass.newInstance();
                for (int i = 0; i < columnCount; i++) {
//               获取列名
                    String columnName = metaData.getColumnName(i + 1);
//                获取列对应的值
                    Object columnValue = rs.getObject(i + 1);
//                    这里获取了列名,就需要根据列名把值填充给entity对象
                    setValue(entity,columnName,columnValue);
                }
                list.add(entity);

            }
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } finally {
            close(connection,psmt,rs);
        }

        return list;
    }

//    通过查询返回一条记录
    protected T load(String sql,Object... params){
        try {
//            获取连接
            connection = getConnection();
//            设置预处理对象
            psmt = connection.prepareStatement(sql);
//            设置参数
            setParams(psmt,params);
//            执行查询
            rs= psmt.executeQuery();
//            处理结果集
//            难点在于如何处理T,我们不知道T是什么类型并且不知道T有多少个参数
//            通过rs可以获取结果集的元数据
//            元数据:描述结果集数据的数据,简单讲,就是这个结果接有哪些列,什么类型等等
            ResultSetMetaData metaData = rs.getMetaData();
            int columnCount = metaData.getColumnCount();
            if (rs.next()){
                T entity = (T)entityClass.newInstance();
                for (int i = 0; i < columnCount; i++) {
//               获取列名
                    String columnName = metaData.getColumnName(i + 1);
//                获取列对应的值
                    Object columnValue = rs.getObject(i + 1);
//                    这里获取了列名,就需要根据列名把值填充给entity对象
                    setValue(entity,columnName,columnValue);
                }
                return entity;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } finally {
            close(connection,psmt,rs);
        }
        return null;
    }

//    执行复杂的查询,返回统计结果,这个方法暂时没有用到
    protected Object[] executeComplexQuery(String sql,Object... params){

        try {
//            获取连接
            connection = getConnection();
//            设置预处理对象
            psmt = connection.prepareStatement(sql);
//            设置参数
            setParams(psmt,params);
//            执行查询
            rs= psmt.executeQuery();
//            处理结果集
//            难点在于如何处理T,我们不知道T是什么类型并且不知道T有多少个参数
//            通过rs可以获取结果集的元数据
//            元数据:描述结果集数据的数据,简单讲,就是这个结果接有哪些列,什么类型等等
            ResultSetMetaData metaData = rs.getMetaData();
            int columnCount = metaData.getColumnCount();
            if (rs.next()){
                Object[] columnValueArr = new Object[columnCount];
                for (int i = 0; i < columnCount; i++) {
//                获取列对应的值
                    Object columnValue = rs.getObject(i + 1);
                    columnValueArr[i]=columnValue;
                }
                return columnValueArr;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            close(connection,psmt,rs);
        }
        return null;
    }

}

3 数据库连接池 3.1 概述和优势

之前的的连接都是用的时候才创建连接,当使用完成之后就销毁,这样效率很低,浪费资源。

使用数据库连接池就可以先创建多个连接对象放到连接池中,等到使用的时候直接从连接池中取出来,用完之后归还。

3.1.1 好处:
  1. 响应时间更快
  2. 连接对象的利用率更高
3.1.2详细配置参数:

不需要背,只是参考,记住主要用的几个就可以了

配置缺省说明
name配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this)
url连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username连接数据库的用户名
password连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/使用ConfigFilter
driverClassName根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)
initialSize0初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive8最大连接池数量
maxIdle8已经不再使用,配置了也没效果
minIdle最小连接池数量
maxWait获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatementsfalse是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxOpenPreparedStatements-1要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
testOnBorrowtrue申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturnfalse归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testWhileIdlefalse建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis
connectionInitSqls物理连接初始化的时候执行的sql
exceptionSorter根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
proxyFilters类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系
3.2 使用 3.2.1 基础使用

将对应的jar引入到项目中之后,创建DruidDataSource对象,进行连接即可。

注意:

  1. 被close的连接对象并没有真正关闭,而是将状态重新设置为空闲状态放回池子
  2. 没有被close的连接对象会被一直占用,那么下次继续获取连接对象,是不会获取到这个对象的
  3. 对于conn1对象指向别的东西,原始情况下jvm会回收conn1之前指向的对象,但是连接池中的连接默认有一个变量指着,所以不会被回收
package com.zylai.jdbc;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;

import java.sql.Connection;
import java.sql.SQLException;



public class Demo02Druid {
    public static void main(String[] args) throws SQLException {

        DruidDataSource dataSource = new DruidDataSource();

        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/fruitdb?&useSSL=false&useUnicode=true&characterEncoding=utf-8");
        dataSource.setUsername("root");
        dataSource.setPassword("root");

//        证明两点:
//        1.被close的连接对象并没有真正关闭,而是将状态重新设置为空闲状态放回池子
//        2.没有被close的连接对象会被一直占用,那么下次继续获取连接对象,是不会获取到这个对象的
//        对于conn1对象指向别的东西,原始情况下jvm会回收conn1之前指向的对象,但是连接池中的连接默认有一个变量指着,所以不会被回收

        for (int i = 0; i < 5; i++) {
            Connection conn1 = dataSource.getConnection();
            Connection conn2 = dataSource.getConnection();

            System.out.println(conn1);
            System.out.println(conn2);

            if(i%3==0){
                conn1.close();
                conn2.close();
            }
        }
    }

}
3.2.2 验证连接池的部分参数

这里以最大连接数和最长等待时间为例,当获取完所有的连接之后,再去获取连接超过指定的等待时间会抛出异常

package com.zylai.jdbc;

import com.alibaba.druid.pool.DruidDataSource;

import java.sql.Connection;
import java.sql.SQLException;



public class Demo03Druid {
    public static void main(String[] args) throws SQLException {

        DruidDataSource dataSource = new DruidDataSource();

        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/fruitdb?&useSSL=false&useUnicode=true&characterEncoding=utf-8");
        dataSource.setUsername("root");
        dataSource.setPassword("root");

        dataSource.setInitialSize(2);
        dataSource.setMaxActive(5);
        dataSource.setMaxWait(5000);


        for (int i = 0; i < 10; i++) {
            Connection conn1 = dataSource.getConnection();

            System.out.println(i+" "+conn1);

        }
    }

}

3.2.3 读取配置文件信息创建连接池

在实际开发中,比如数据库url,密码用户等信息都是写在配置文件中,不会直接写在代码中进行硬编码

package com.zylai.jdbc;

import com.alibaba.druid.pool.DruidDataSource;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;


public class Demo04Druid {
    public static void main(String[] args) throws SQLException, IOException {

        Properties properties = new Properties();
        InputStream is = Demo04Druid.class.getClassLoader().getResourceAsStream("jdbc.properties");
        properties.load(is);
        DruidDataSource dataSource = new DruidDataSource();

        dataSource.setDriverClassName(properties.getProperty("jdbc.driverClassName"));
        dataSource.setUrl(properties.getProperty("jdbc.url"));
        dataSource.setUsername(properties.getProperty("jdbc.username"));
        dataSource.setPassword(properties.getProperty("jdbc.pwd"));

        dataSource.setInitialSize(Integer.parseInt(properties.getProperty("druid.initialSize")));
        dataSource.setMaxActive(Integer.parseInt(properties.getProperty("druid.maxActive")));
        dataSource.setMaxWait(Long.parseLong(properties.getProperty("druid.maxWait")));
        
        for (int i = 0; i < 10; i++) {
            Connection conn1 = dataSource.getConnection();
            System.out.println(i+" "+conn1);
        }
    }

}

配置文件jdbc.properties:

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/fruitdb?&useSSL=false&useUnicode=true&characterEncoding=utf-8
jdbc.username=root
jdbc.pwd=root

druid.initialSize=2
druid.maxActive=5
druid.maxWait=5000
3.2.4 直接通过配置文件建立连接池

通过DruidDataSourceFactory.createDataSource(properties);返回**DataSource接口**,直接通过配置文件建立好了连接池。不过这里对于配置文件的格式有要求。

package com.zylai.jdbc;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;


public class Demo05Druid {
    public static void main(String[] args) throws Exception {

        Properties properties = new Properties();
        InputStream is = Demo05Druid.class.getClassLoader().getResourceAsStream("jdbc2.properties");
        properties.load(is);

        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
        
        for (int i = 0; i < 10; i++) {
            Connection conn1 = dataSource.getConnection();
            System.out.println(i+" "+conn1);
        }
    }
}

配置文件的格式应该一致

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/fruitdb?&useSSL=false&useUnicode=true&characterEncoding=utf-8
username=root
password=root

initialSize=2
maxActive=5
maxWait=5000
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/987716.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号