Java MySql连接

MySql连接

  • MySql驱动包加载

  • 数据库操作的一般过程

  • 连接MySql

  • 创建Statement对象

  • 执行SQL语句

  • 处理ResultSet对象

  • 释放资源

  • 增删改查代码

  • 数据访问对象模式或DAO模式

  • c3p0数据库连接池

  • QueryRunner的使用

  • 错误例子

一. MySql驱动包加载

MySql8.0以上驱动包下载:mysql-connector-java-8.0.16.jar

驱动包在Android Studio这个IDE平台上如何使用:

  • 在Android Studio的Android工程中添加Java代码模块。

  • 调整目录结构为工程目录结构,找到Java代码模块的libs目录或者Android代码的libs目录。

  • 下载驱动包,复制到libs目录下面,右击Add As Library后,选择你要导入到的模块module,之后才可以使用。




二. 数据库操作的一般过程

1、调用Class.forName()方法加载驱动程序。

2、连接数据库,调用DriverManager对象的getConnection()方法,获得一个Connection对象。

3、创建一个Statement对象,准备一个SQL语句,这个SQL语句可以是Statement对象(立即执行的的语句)、PreparedStatement语句(预编译的语句)或CallableStatement对象(存储过程调用的语句)。

4、调用excuteQuery()等方法执行SQL语句,并将结果保存在ResultSet对象;或者调用executeUpdate()等方法执行SQL语句,不返回ResultSet对象的结果。

5、对返回的ResultSet对象进行显示等相当的处理。

6、释放资源。

三. 连接MySql

  • 工程中导入驱动程序,通过java.lang.Class类的静态方法forName(String className加载想要连接的数据库的驱动到JVM(Java虚拟机)。如果成功加载,那么Driver的实例就会注册到DriverManager类中。

  • 创建数据库连接,调用DriverManager对象的getConnection()方法,获得一个Connection对象。连接URL定义了连接数据库时的协议、子协议、数据源标识。

  • 连接URL书写形式:协议:子协议:数据源标识。协议:在JDBC中总是以jdbc开始。 子协议:是桥连接的驱动程序或是数据库管理系统名称。 数据源标识:标记找到数据库来源的地址与连接端口。

  • MySql8.0之后还要加上?useSSL=false&serverTimezone=UTC来声明是否使用 SSL 安全验证及指定服务器上的时区。

//JDBC驱动
public static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";

//数据库URL
public static final String DB_URL = "jdbc:mysql://localhost:3306/test_java?useSSL=false&serverTimezone=UTC";

//数据库的用户名和密码
public static final String USER = "root";
public static final String PASS = "123456";

//连接数据库
public static void connectToMySql(){
   //注册JDBC驱动
   try {
       Class.forName(JDBC_DRIVER);
       System.out.println("注册JDBC成功");
   } catch (ClassNotFoundException e) {
       System.out.println("JDBC驱动注册错误:"+e.getMessage());
   }

   //打开连接
   try {
       connection = DriverManager.getConnection(DB_URL, USER, PASS);
       System.out.println("数据库连接成功");
   } catch (SQLException e) {
       System.out.println("数据库连接错误:"+e.getMessage());
   }
}

上述 test_java 是我们连接的数据库名称,USER是连接的名称,PASS是连接的密码

USER就是用户名,PASS就是密码

test_java是我们创建的一个连接下面的数据库

四. 创建Statement对象

要执行SQL语句,必须获得java.sql.Statement实例,Statement实例分为以下3种类型:

  • 执行静态SQL语句。通常通过Statement实例实现。在执行的时候需要往执行SQL语句的方法中传入SQL语句。

  • 执行动态SQL语句。通常通过PreparedStatement实例实现。在执行的时候不需要往执行SQL语句的方法中传入SQL语句。

  • 执行数据库存储过程。通常通过CallableStatement实例实现。

Statement stmt = con.createStatement() ;

reparedStatement pstmt = con.prepareStatement(sql) ;  

CallableStatement cstmt = con.prepareCall("{CALL demoSp(? , ?)}") ; 

五. 执行SQL语句

Statement接口提供了三种执行SQL语句的方法:executeQuery 、executeUpdate 、execute :

  • ResultSet executeQuery(String sqlString):执行查询数据库的SQL语句 ,返回一个结果集(ResultSet)对象。

  • int executeUpdate(String sqlString):用于执行INSERT、UPDATE或 DELETE语句以及SQL DDL语句,如:CREATE TABLE和DROP TABLE等

  • execute(sqlString):用于执行返回多个结果集、多个更新计数或二者组合的语句。

六. 处理ResultSet对象

ResultSet包含符合SQL语句中条件的所有行,并且它通过一套get方法提供了对这些行中数据的访问。

//查询数据库中某一条数据
public static void select(int id){
   try {
       //创建sql语句
       String sql = "select * from testTable where id = ?";

       //创建用于将sql语句发送到数据库的准备容器
       PreparedStatement preparedStatement = connection.prepareStatement(sql);

       //绑定sql语句中不确定的参数
       preparedStatement.setInt(1,id);

       //将sql语句上传至数据库中执行
       ResultSet resultSet = preparedStatement.executeQuery();

       //循环遍历内容
       while (resultSet.next()){
           //创建一个Worker类
           mWorker worker = new mWorker();

           //通过字段检索
           worker.id = resultSet.getInt("id");
           worker.name = resultSet.getString("name");
           worker.age = resultSet.getInt("age");

           //加入数组
           workers.add(worker);
       }
   } catch (SQLException e) {
       System.out.println("查询数据失败:"+e.getMessage());
   }
}

//查询数据库中所有数据
public static void selectAll(){
   try {
       //创建sql语句
       String sql = "select * from testTable";

       //创建用于将SQL语句发送到数据库的容器
       Statement statement = connection.createStatement();

       //将sql语句上传至数据库中执行
       ResultSet resultSet = statement.executeQuery(sql);

       //循环内容
       while (resultSet.next()){
           //创建一个Worker类
           mWorker worker = new mWorker();

           //通过字段检索
          worker.id = resultSet.getInt("id");
           worker.name = resultSet.getString("name");
           worker.age = resultSet.getInt("age");

           //加入数组
           workers.add(worker);
       }

       //关掉结果Set
       resultSet.close();

       //关掉容器
       statement.close();

       System.out.println("查询数据成功");
   } catch (SQLException e) {
       System.out.println("查询数据失败:"+e.getMessage());
   }

}

七. 释放资源

操作完成以后要把所有使用的JDBC对象全都关闭,以释放JDBC资源,关闭顺序和声明顺序相反:

  • 关闭记录集:ResultSet对象
  • 关闭声明:Statementt对象
  • 关闭连接对象:Connection对象

八. 增删改查代码

public class Test {
    public static void main(String[] args) {

        DataManager.connectToMySql();

        //DataManager.insert("xyq",18);

        //DataManager.update(6,"WXC",20);

        //DataManager.delete(6);

        //DataManager.selectAll();
        //System.out.println(DataManager.workers);

        //DataManager.select(1);
        //System.out.println(DataManager.workers);
        
        DataManager.closeConnect();
    }
}

//模型
class mWorker {
    //成员变量
    public int id;
    public String name;
    public int age;

    @Override
    public String toString() {
        return "mWorker{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

/**
 * 管理数据库的相关操作
 */
class DataManager {

    //保存查询的数据
    public static ArrayList<mWorker> workers = new ArrayList<>();

    //JDBC驱动
    public static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";

    //数据库URL
    public static final String DB_URL = "jdbc:mysql://localhost:3306/test_java?useSSL=false&serverTimezone=UTC";

    //数据库的用户名和密码
    public static final String USER = "root";
    public static final String PASS = "123456";

    //数据库连接对象
    public static Connection connection;

    //连接数据库
    public static void connectToMySql(){
        //注册JDBC驱动
        try {
            Class.forName(JDBC_DRIVER);
            System.out.println("注册JDBC成功");
        } catch (ClassNotFoundException e) {
            System.out.println("JDBC驱动注册错误:"+e.getMessage());
        }

        //打开连接
        try {
            connection = DriverManager.getConnection(DB_URL, USER, PASS);
            System.out.println("数据库连接成功");
        } catch (SQLException e) {
            System.out.println("数据库连接错误:"+e.getMessage());
        }
    }

    //向数据库中插入数据
    public static void insert(String name,int age){
        try {
            //创建SQL语句
            String insertSQL = "insert into testTable(name,age) values(?,?)";

            //创建用于将SQL语句发送到数据库的准备容器
            PreparedStatement preparedStatement = connection.prepareStatement(insertSQL);

            //绑定sql语句中不确定的参数
            preparedStatement.setString(1,name);
            preparedStatement.setInt(2,age);

            //将SQL语句上传至数据库执行
            preparedStatement.executeUpdate();

            //关掉容器
            preparedStatement.close();

            System.out.println("数据插入成功");
        } catch (SQLException e) {
            System.out.println("数据插入失败:"+e.getMessage());
        }
    }

    //修改数据库中数据
    public static void update(int id,String name,int age){
        try {
            //创建SQL语句
            String sql = "update testTable set name = ?,age = ? where id = ?";

            //创建用于将SQL语句发送到数据库的准备容器
            PreparedStatement preparedStatement = connection.prepareStatement(sql);

            //绑定sql语句中不确定的参数
            preparedStatement.setString(1,name);
            preparedStatement.setInt(2,age);
            preparedStatement.setInt(3,id);

            //将sql语句上传至数据库中执行
            preparedStatement.executeUpdate();

            //关掉容器
            preparedStatement.close();

            System.out.println("修改数据成功");
        } catch (SQLException e) {

            System.out.println("修改数据失败:"+e.getMessage());
        }
    }

    //查询数据库中某一条数据
    public static void select(int id){
        try {
            //创建sql语句
            String sql = "select * from testTable where id = ?";

            //创建用于将sql语句发送到数据库的准备容器
            PreparedStatement preparedStatement = connection.prepareStatement(sql);

            //绑定sql语句中不确定的参数
            preparedStatement.setInt(1,id);

            //将sql语句上传至数据库中执行
            ResultSet resultSet = preparedStatement.executeQuery();

            //循环遍历内容
            while (resultSet.next()){
                //创建一个Worker类
                mWorker worker = new mWorker();

                //通过字段检索
                worker.id = resultSet.getInt("id");
                worker.name = resultSet.getString("name");
                worker.age = resultSet.getInt("age");

                //加入数组
                workers.add(worker);
            }
        } catch (SQLException e) {
            System.out.println("查询数据失败:"+e.getMessage());
        }
    }

    //查询数据库中所有数据
    public static void selectAll(){
        try {
            //创建sql语句
            String sql = "select * from testTable";

            //创建用于将SQL语句发送到数据库的容器
            Statement statement = connection.createStatement();

            //将sql语句上传至数据库中执行
            ResultSet resultSet = statement.executeQuery(sql);

            //循环内容
            while (resultSet.next()){
                //创建一个Worker类
                mWorker worker = new mWorker();

                //通过字段检索
                worker.id = resultSet.getInt("id");
                worker.name = resultSet.getString("name");
                worker.age = resultSet.getInt("age");

                //加入数组
                workers.add(worker);
            }

            //关掉结果Set
            resultSet.close();

            //关掉容器
            statement.close();

            System.out.println("查询数据成功");
        } catch (SQLException e) {
            System.out.println("查询数据失败:"+e.getMessage());
        }

    }

    //删除数据库中数据
    public static void delete(int id){
        try {
            //创建sql语句
            String sql = "delete from testTable where id = ?";

            //创建用于将SQL语句发送到数据库的准备容器
            PreparedStatement preparedStatement = connection.prepareStatement(sql);

            //绑定sql语句中不确定的参数
            preparedStatement.setInt(1,id);

            //将sql语句上传至数据库中执行
            preparedStatement.executeUpdate();

            //关掉容器
            preparedStatement.close();

            System.out.println("删除数据成功");
        } catch (SQLException e) {
            System.out.println("删除数据失败:"+e.getMessage());
        }
    }

    //关闭连接
    public static void closeConnect(){
        try {
            //关闭连接
            connection.close();

            System.out.println("关闭连接成功");
        } catch (SQLException e) {
            System.out.println("关闭连接失败:"+e.getMessage());
        }
    }
}

九. 数据访问对象模式或DAO模式

1.DAO简介:

DAO (DataAccessobjects 数据存取对象)是指位于业务逻辑和持久化数据之间实现对持久化数据的访问。通俗来讲,就是将数据库操作都封装起来。

2.DAO 模式的优势:

  • 隔离了数据访问代码和业务逻辑代码。业务逻辑代码直接调用DAO方法即可,完全感觉不到数据库表的存在。分工明确,数据访问层代码变化不影响业务逻辑代码,这符合单一职能原则,降低了藕合性,提高了可复用性。

  • 隔离了不同数据库实现。采用面向接口编程,如果底层数据库变化,如由 MySQL 变成 Oracle 只要增加 DAO 接口的新实现类即可,原有 MySQ 实现不用修改。这符合 "开-闭" 原则。该原则降低了代码的藕合性,提高了代码扩展性和系统的可移植性。

3.一个典型的DAO 模式主要由以下几部分组成:

  • DAO接口: 把对数据库的所有操作定义成抽象方法,可以提供多种实现。

  • DAO 实现类: 针对不同数据库给出DAO接口定义方法的具体实现。

  • 实体类:用于存放与传输对象数据。

  • 数据库连接和关闭工具类: 避免了数据库连接和关闭代码的重复使用,方便修改。

4.代码例子

Dao接口:

public interface PetDao {
   /**
    * 查询所有宠物
    */
   List<Pet> findAllPets() throws Exception;
}

Dao实现类:

public class PetDaoImpl extends BaseDao implements PetDao {
   /**
    * 查询所有宠物
    */
   public List<Pet> findAllPets() throws Exception {
       Connection conn=BaseDao.getConnection();
       String sql="select * from pet";
       PreparedStatement stmt= conn.prepareStatement(sql);
       ResultSet rs=    stmt.executeQuery();
       List<Pet> petList=new ArrayList<Pet>();
       while(rs.next()) {
           Pet pet=new Pet(
                   rs.getInt("id"),
                   rs.getInt("owner_id"),
                   rs.getInt("store_id"),
                   rs.getString("name"),
                   rs.getString("type_name"),
                   rs.getInt("health"),
                   rs.getInt("love"),
                   rs.getDate("birthday")
                   );
               petList.add(pet);
       }
       BaseDao.closeAll(conn, stmt, rs);
       return petList;
   }
}

实体类:

public class Pet {
   private Integer id;    
   private Integer ownerId;    //主人ID
   private Integer storeId;    //商店ID
   private String name;    //姓名
   private String typeName;    //类型
   private int health;    //健康值
   private int love;    //爱心值
   private Date birthday;    //生日

  //setter、getter方法
  ....
}

数据库连接关闭工具类

public class BaseDao {
   private static String driver="com.mysql.jdbc.Driver";
   private static String url="jdbc:mysql://127.0.0.1:3306/epet";
   private static String user="root";
   private static String password="root";
       static {
           try {
               Class.forName(driver);
           } catch (ClassNotFoundException e) {
               e.printStackTrace();
           }
       }
       
   public static Connection getConnection() throws SQLException {
       return DriverManager.getConnection(url, user, password);    
   }
   
   public static void closeAll(Connection conn,Statement stmt,ResultSet rs) throws SQLException {
       if(rs!=null) {
           rs.close();
       }
       if(stmt!=null) {
           stmt.close();
       }
       if(conn!=null) {
           conn.close();
       }
   }
   

   public int executeSQL(String preparedSql, Object[] param) throws ClassNotFoundException {
       Connection conn = null;
       PreparedStatement pstmt = null;
       /* 处理SQL,执行SQL */
       try {
           conn = getConnection(); // 得到数据库连接
           pstmt = conn.prepareStatement(preparedSql); // 得到PreparedStatement对象
           if (param != null) {
               for (int i = 0; i < param.length; i++) {
                   pstmt.setObject(i + 1, param[i]); // 为预编译sql设置参数
               }
           }
       ResultSet num = pstmt.executeQuery(); // 执行SQL语句
       } catch (SQLException e) {
          e.printStackTrace(); // 处理SQLException异常
       } finally {
           try {
               BaseDao.closeAll(conn, pstmt, null);
           } catch (SQLException e) {    
               e.printStackTrace();
           }
       }
       return 0;
   }
   
}

十. c3p0数据库连接池

1. 为什么要使用连接池

在Java开发中,使用JDBC操作数据库的四个步骤如下:

  • ①加载数据库驱动程序(Class.forName("数据库驱动类");)
  • ②连接数据库(Connection con = DriverManager.getConnection();)
  • ③操作数据库(PreparedStatement stat = con.prepareStatement(sql);stat.executeQuery();)
  • ④关闭数据库,释放连接(con.close();)

也就是说,所有的用户都需要经过此四步进行操作,但是这四步之中有三步(①加载数据库驱动程序、②连接数据库、④关闭数据库,释放连接)对所有人都是一样的,而所有人只有在操作数据库上是不一样,那么这就造成了性能的损耗。

那么最好的做法是,准备出一个空间,此空间里专门保存着全部的数据库连接,以后用户用数据库操作的时候不用再重新加载驱动、连接数据库之类的,而直接从此空间中取走连接,关闭的时候直接把连接放回到此空间之中。

那么此空间就可以称为连接池(保存所有的数据库连接)

2. 连接池是什么

C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。

连接池是创建和管理一个连接的缓冲池的技术,这些连接准备好被任何需要它们的线程使用。

3.连接池原理

连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等。也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。

4.数据库的连接池

数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。连接池主要由三部分组成:连接池的建立、连接池中连接的使用管理、连接池的关闭。

使用c3p0连接MySql需要导入jar包mysql-connector-java-8.0.16.jar,c3p0-0.9.5.2.jar,mchange-commons-java-0.2.11.jar

5.数据库连接池的三种配置方式

  • 手动在方法里面添加数据库连接,即在set方法里传递数据库连接和配置的参数。

  • 自定义properties文件或xml文件,配置相关数据库信息,然后手动解析。

  • 定义号默认的配置文件,properties文件为c3p0.properties;xml文件为c3p0-config.xml,放入src根目录下,c3p0会自动帮我们解析,只需把参数传入配置文件就可以使用。

① 使用.properties文件手动解析(第二种配置方法)

//测试连接
public class Test {
    public static void main(String[] args) throws SQLException {
       
        //获得一个连接
        Connection connection = JDBCUtils.getConnection();
    }
}

//c3p0.properties文件
c3p0.driverClass=com.mysql.cj.jdbc.Driver
c3p0.jdbcUrl=jdbc:mysql://127.0.0.1:3306/test_java?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
c3p0.user=root
c3p0.password=123456

/**
 * 创建数据库连接池
 */
class JDBCUtils{
    //创建一个ThreadLocal对象,用当前线程作为key
    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    //读取C3P0-config默认配置创建数据库连接池对象
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
 
    //读取properties类里面的配置数据
    private static Properties config = new Properties();

    //获取数据库连接池对象
    public static DataSource getDataSource(){
        return dataSource;
    }

    //从连接池中获取连接
    public static Connection getConnection() throws SQLException {
        //从集合中获取连接对象
        Connection con = threadLocal.get();

        //如果集合中没有连接对象
        if (con == null){
            try {
                config.load(new FileInputStream("G:\\Android_Studio\\AndroidStudioProjects\\JavaCode\\Java\\src\\main\\java\\swu\\xl\\day16_self_MySql_dbUtil\\c3p0.properties"));
            } catch (IOException e) {
                e.printStackTrace();
            }

            try {
                //设置数据
                dataSource.setDriverClass(config.getProperty("c3p0.driverClass"));
                dataSource.setJdbcUrl(config.getProperty("c3p0.jdbcUrl"));
                dataSource.setUser(config.getProperty("c3p0.user"));
                dataSource.setPassword(config.getProperty("c3p0.password"));
                dataSource.setInitialPoolSize(10);
            } catch (PropertyVetoException e) {
                System.out.println("数据设置失败");
            }

            //获取连接对象
            con = dataSource.getConnection();

            //将con放入集合中
            threadLocal.set(con);

            System.out.println("第一次创建连接");
        }

        return con;
    }

    //开启事务
    public static void startTransaction() throws SQLException {
        //获取连接
        Connection connection = getConnection();

        //开启事务 false保证数据的完整性
        connection.setAutoCommit(false);
    }

    //提交事务
    public static void commitAndClose() throws SQLException {
        //获取连接
        Connection connection = getConnection();

        //提交事务
        connection.commit();

        //关闭且移除
        closeConn(connection);
    }

    //回顾事务
    public static void rollbackAndClose() throws SQLException {
        //获取连接
        Connection connection = getConnection();

        //事务回顾
        connection.rollback();

        //关闭且移除
        closeConn(connection);
    }

    //释放资源
    public static void closeResource(Connection conn, Statement st, ResultSet rs) {
        closeResultSet(rs);
        closeStatement(st);
        closeConn(conn);
    }

    //释放连接
    public static void closeConn(Connection conn) {
        if (conn != null) {
            try {
                //释放连接
                conn.close();

                //和当前线程解绑
                threadLocal.remove();
            } catch (SQLException e) {
                e.printStackTrace();
            }

            conn = null;
        }
    }

    //释放语句执行者
    public static void closeStatement(Statement st) {
        if (st != null) {
            try {
                st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }

            st = null;
        }
    }

    //释放结果集
    public static void closeResultSet(ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }

            rs = null;
        }
    }
}

② 使用c3p0-config.xml文件自定义位置自动解析(第三种配置方法)

//测试连接
public class Test {
    public static void main(String[] args) throws SQLException {
        //设置xml自定义路径 
        System.setProperty("com.mchange.v2.c3p0.cfg.xml","G:\\Android_Studio\\AndroidStudioProjects\\JavaCode\\Java\\src\\main\\java\\swu\\xl\\day16_self_MySql_dbUtil\\c3p0-config.xml");
        //获得一个连接        
        Connection connection = JDBCUtils.getConnection();
    }
}

//c3p0-config.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<named-config name="mysql">
    <property name="user">root</property>
    <property name="password">123456</property>
    <property name="jdbcUrl">jdbc:mysql://localhost:3306/test_java?useSSL=false&amp;serverTimezone=UTC&amp;characterEncoding=UTF-8</property>
    <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
    <property name="initialPoolSize">10</property>
    <property name="minPoolSize">5</property>
    <property name="maxPoolSize">30</property>
</named-config>
</c3p0-config>


/**
 * 创建数据库连接池
 */
class JDBCUtils{
    //创建一个ThreadLocal对象,用当前线程作为key
    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    //读取C3P0-config默认配置创建数据库连接池对象
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource("mysql");

    //获取数据库连接池对象
    public static DataSource getDataSource(){
        return dataSource;
    }

    //从连接池中获取连接
    public static Connection getConnection() throws SQLException {
        //从集合中获取连接对象
        Connection con = threadLocal.get();

        //如果集合中没有连接对象
        if (con == null){
            //获取连接对象
            con = dataSource.getConnection();

            //将con放入集合中
            threadLocal.set(con);

            System.out.println("第一次创建连接");
        }

        return con;
    }

     //开启事务
    public static void startTransaction() throws SQLException {
        ///...
    }

    //提交事务
    public static void commitAndClose() throws SQLException {
        //...
    }

    //回顾事务
    public static void rollbackAndClose() throws SQLException {
       //...
    }

    //释放资源
    public static void closeResource(Connection conn, Statement st, ResultSet rs) {
       //...
    }

    //释放连接
    public static void closeConn(Connection conn) {
       //....
    }

    //释放语句执行者
    public static void closeStatement(Statement st) {
        //....
    }

    //释放结果集
    public static void closeResultSet(ResultSet rs) {
       //...
    }
}

自动读取配置文件(第三种配置方法)
将c3p0.properties或者c3p0-config.xml文件存放到指定的位置,系统自动读取配置文件。一般的开发平台是存放在src目录下面。在Android Studio中是project是按模块划分的,每个模块有一个build文件夹存放编译时所需要的文件,我们将我们的配置文件放在build/classes/java/main文件夹下面就可以被自动读取到。千万不要放在src目录下面,读取不到数据。

//测试连接
public class c3p0Default {
    public static void main(String[] args) throws SQLException {
        Connection connection = DBUtils.getConnection();
    }
}

//两个配置文件任选一个

//c3p0.properties文件
c3p0.driverClass=com.mysql.cj.jdbc.Driver
c3p0.jdbcUrl=jdbc:mysql://127.0.0.1:3306/test_java?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
c3p0.user=root
c3p0.password=123456

//c3p0-config.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <default-config>
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/test_java?useSSL=false&amp;serverTimezone=UTC</property>
        <property name="user">root</property>
        <property name="password">123456</property>
        <property name="initialPoolSize">5</property>
        <property name="maxPoolSize">20</property>
    </default-config>
</c3p0-config>

/**
 * 创建数据库连接池
 */
class JDBCUtils{
    //创建一个ThreadLocal对象,用当前线程作为key
    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    //读取C3P0-config默认配置创建数据库连接池对象
    private static ComboPooledDataSource dataSource = new ComboPooledDataSource();

    //获取数据库连接池对象
    public static DataSource getDataSource(){
        return dataSource;
    }

    //从连接池中获取连接
    public static Connection getConnection() throws SQLException {
        //从集合中获取连接对象
        Connection con = threadLocal.get();

        //如果集合中没有连接对象
        if (con == null){
            //获取连接对象
            con = dataSource.getConnection();

            //将con放入集合中
            threadLocal.set(con);

            System.out.println("第一次创建连接");
        }

        return con;
    }

    //开启事务
    public static void startTransaction() throws SQLException {
        ///...
    }

    //提交事务
    public static void commitAndClose() throws SQLException {
        //...
    }

    //回顾事务
    public static void rollbackAndClose() throws SQLException {
       //...
    }

    //释放资源
    public static void closeResource(Connection conn, Statement st, ResultSet rs) {
       //...
    }

    //释放连接
    public static void closeConn(Connection conn) {
       //....
    }

    //释放语句执行者
    public static void closeStatement(Statement st) {
        //....
    }

    //释放结果集
    public static void closeResultSet(ResultSet rs) {
       //...
}

分析使用:

  • 第一种方式,移植性差,修改麻烦。

  • 第二种方式需要手动读取,虽然移植性有了提升,但是

  • 第三种方式分为使用c3p0.propertiesc3p0-config.xml。如果你只有一个数据源,你都可以使用,c3p0.properties文件会简单一点。如果你有多个数据源则使用c3p0-config.xml。

  • 使用c3p0.propertiesc3p0-config.xml,都需要将文件放在src目录下面,c3p0-config.xml可以通过System.setProperty("com.mchange.v2.c3p0.cfg.xml",路径字符串);来自定义存放位置。

  • c3p0.properties的具体配置可以参考官方文件

  • 在xml文件里面用&amp;代替&,不然会出错。

  • c3p0-config.xml的具体配置可以参考下面的例子或者官网。注意只有一个数据源时,标签使用<default-config>,初始化的时候使用ComboPooledDataSource dataSource = new ComboPooledDataSource()。多个数据源的时候,标签使用<named-config name="自定义名">,初始化的时候使用ComboPooledDataSource dataSource = new ComboPooledDataSource("自定义名")

<!-- 单个数据源的xml配置 -->
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
   <default-config>
       <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
       <property name="jdbcUrl">jdbc:mysql://localhost:3306/test_java?>useSSL=false&amp;serverTimezone=UTC</property>
       <property name="user">root</property>
       <property name="password">123456</property>
       <property name="initialPoolSize">5</property>
      <property name="minPoolSize">5</property>
       <property name="maxPoolSize">20</property>
   </default-config>
</c3p0-config>
<!-- 多个数据源的xml配置 -->
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
 <named-config name="自定义名">
   <property name="user">root</property>
   <property name="password">123456</property>
   <property name="jdbcUrl">jdbc:mysql://localhost:3306/test_java?useSSL=false&amp;serverTimezone=UTC&amp;characterEncoding=UTF-8</property>
   <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
   <property name="initialPoolSize">10</property>
   <property name="minPoolSize">5</property>
   <property name="maxPoolSize">30</property>
 </named-config>
</c3p0-config>
<!-- 详细的xml配置 -->
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
 <default-config>
   <!-- 配置数据库用户名 -->
   <property name="user">root</property>
   <!-- 配置数据库密码 -->
   <property name="password">123456</property>
   <!-- 配置数据库链接地址 -->
   <property name="jdbcUrl">jdbc:mysql://localhost:3306/test_java?useSSL=false&amp;serverTimezone=UTC&amp;characterEncoding=UTF-8</property>
   <!-- 配置数据库驱动 -->
   <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
   <!-- 数据库连接池一次性向数据库要多少个连接对象 -->
   <property name="acquireIncrement">20</property>
   <!-- 初始化连接数 -->
   <property name="initialPoolSize">10</property>
   <!-- 最小连接数 -->
   <property name="minPoolSize">5</property>
   <!--连接池中保留的最大连接数。Default: 15 -->
   <property name="maxPoolSize">30</property>
   <!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements 属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default:0 -->
   <property name="maxStatements">0</property>
   <!--maxStatementsPerConnection定义了连接池内单个连接所拥有的最大缓存statements数。Default: 0 -->
   <property name="maxStatementsPerConnection">0</property>
   <!--c3p0是异步操作的,缓慢的JDBC操作通过帮助进程完成。扩展这些操作可以有效的提升性能 通过多线程实现多个操作同时被执行。Default:3 -->
   <property name="numHelperThreads">3</property>
   <!--用户修改系统配置参数执行前最多等待300秒。Default: 300 -->
   <property name="propertyCycle">3</property>
   <!-- 获取连接超时设置 默认是一直等待单位毫秒 -->
   <property name="checkoutTimeout">1000</property>
   <!--每多少秒检查所有连接池中的空闲连接。Default: 0 -->
   <property name="idleConnectionTestPeriod">3</property>
   <!--最大空闲时间,多少秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
   <property name="maxIdleTime">10</property>
   <!--配置连接的生存时间,超过这个时间的连接将由连接池自动断开丢弃掉。当然正在使用的连接不会马上断开,而是等待它close再断开。配置为0的时候则不会对连接的生存时间进行限制。 -->
   <property name="maxIdleTimeExcessConnections">5</property>
   <!--两次连接中间隔时间,单位毫秒。Default: 1000 -->
   <property name="acquireRetryDelay">1000</property>
   <!--c3p0将建一张名为Test的空表,并使用其自带的查询语句进行测试。如果定义了这个参数那么属性preferredTestQuery将被忽略。你不能在这张Test表上进行任何操作,它将只供c3p0测试使用。Default: null -->
   <property name="automaticTestTable">Test</property>
   <!-- 获取connnection时测试是否有效 -->
   <property name="testConnectionOnCheckin">true</property>
 </default-config>
</c3p0-config>

十一. QueryRunner的使用

1.什么是 QueryRunner

QueryRunner是Dbutils中的一个方法,DbUtils(org.apache.commons.dbutils.DbUtils)是Apache组织提供的一个对JDBC进行简单封装的开源工具类库,封装了JDBC的代码,开发dao层代码的简单框架。使用该工具包需要导入jar包commons-dbutils-1.7.jar

2.为什么使用 Dbutils

在Dbutils出现之前在dao层中使用的技术是JDBC,有大量的重复劳动,开发的周期长,效率低。

3.Dbutils三个核心类

  • DbUtils:连接数据库对象----JDBC辅助方法的集合类,线程安全。主要负责装载驱动、关闭连接的常规工作。可以自行添加一个JDBCUtils工具类---用来获取c3p0连接池对象。

  • QueryRunner:SQL语句的操作对象,可以设置查询结果集的封装策略,线程安全。

构造方法:
(1)QueryRunner():创建一个与数据库无关的QueryRunner对象,后期再操作数据库的会后,需要手动给一个Connection对象,它可以手动控制事务。
Connection.setAutoCommit(false);     设置手动管理事务
Connection.commit();     提交事务

(2)QueryRunner(DataSource ds):创建一个与数据库关联的queryRunner对象,后期再操作数据库的时候,不需要Connection对象,自动管理事务。
DataSource:数据库连接池对象。

构造函数与增删改查方法的组合:
QueryRunner()
 update(Connection conn, String sql, Object... params)
 query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params)

QueryRunner(DataSource ds)     
 update(String sql, Object... params)
 query(String sql, ResultSetHandler<T> rsh, Object... params)
  • ResultSetHandle:封装数据的策略对象------将封装结果集中的数据,转换到另一个对象。ResultSetHandler接口执行处理一个结果集对象,将数据转变并处理为任何一种形式,供其他应用使用。实现类如下:
ArrayHandler:将结果集中的第一条记录封装到一个Object[]数组中,数组中的每一个元素就是这条记录中的每一个字段的值
ArrayListHandler:将结果集中的每一条记录都封装到一个Object[]数组中,将这些数组在封装到List集合中。
BeanHandler:将结果集中第一条记录封装到一个指定的javaBean对象中。
BeanListHandler:将结果集中每一条记录封装到指定的javaBean对象中,将这些javaBean对象再封装到List集合中//重点
MapHandler:将结果集第一行封装到Map集合中,Key 列名, Value 该列数据//重点
MapListHandler:将结果集第一行封装到Map集合中,Key 列名, Value 该列数据,Map集合存储到List集合
ColumnListHandler:将结果集中指定的列的字段值,封装到一个List集合中
KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里(List<Map>),再把这些map再存到一个map里,其key为指定的列。
ScalarHandler:将结果集第一行的某一列放到某个对象中,它是用于单数据。例如select count(*) from 表操作。//重点

4.实际使用

使用QueryRunner(DataSource ds):创建一个与数据库关联的queryRunner对象,自动管理事务,所以不需要自己释放资源。

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

       //设置加载的配置文件的路径
       >System.setProperty("com.mchange.v2.c3p0.cfg.xml","G:\\Android_Studio\\AndroidStudioProjects\\JavaCode\\Java\\src\\main\\java\\swu\\xl\\day16_self_MySql_dbUtil\\c3p0-config.xml");

       //创建QueryRunner
       QueryRunner queryRunner = new QueryRunner(JDBCUtils.getDataSource());

       //查询语句
       String sql = "select * from testTable";
       List<Worker> query = queryRunner.query(sql, new BeanListHandler<>(Worker.class));
       System.out.println(query);

       sql = "select * from testTable where id = ?";
       List<Worker> query1 = queryRunner.query(sql, new BeanListHandler<>(Worker.class), 1);
       System.out.println(query1);

       //插入语句
       sql = "insert into testTable(name,age) values(?,?)";
       queryRunner.update(sql,"jack",45);

       //修改语句
       sql = "update testTable set name = ?,age = ? where id = ?";
       queryRunner.update(sql,"jim",88,9);

       //删除语句
       sql = "delete from testTable where id = ?";
       queryRunner.update(sql,9);

       //查询数量
       sql = "select count(*) from testTable where id = ?";
       Object query2 = queryRunner.query(sql, new ScalarHandler<>(), 1);
       System.out.println(query2);

   }
}


/**
* 创建数据库连接池
*/
class JDBCUtils{
   //创建一个ThreadLocal对象,用当前线程作为key
   private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

   //读取C3P0-config默认配置创建数据库连接池对象
   private static ComboPooledDataSource dataSource = new ComboPooledDataSource("mysql");

   //获取数据库连接池对象
   public static DataSource getDataSource(){
       return dataSource;
   }

   //从连接池中获取连接
   public static Connection getConnection() throws SQLException {
       //从集合中获取连接对象
       Connection con = threadLocal.get();

       //如果集合中没有连接对象
       if (con == null){
           //获取连接对象
           con = dataSource.getConnection();

           //将con放入集合中
           threadLocal.set(con);

           System.out.println("第一次创建连接");
       }

       return con;
   }

   //开启事务
   public static void startTransaction() throws SQLException {
       //获取连接
       Connection connection = getConnection();

       //开启事务 false保证数据的完整性
       connection.setAutoCommit(false);
   }

   //提交事务
   public static void commitAndClose() throws SQLException {
       //获取连接
       Connection connection = getConnection();

       //提交事务
       connection.commit();

       //关闭且移除
       closeConn(connection);
   }

   //回顾事务
   public static void rollbackAndClose() throws SQLException {
       //获取连接
       Connection connection = getConnection();

       //事务回顾
       connection.rollback();

       //关闭且移除
       closeConn(connection);
   }

   //释放资源
   public static void closeResource(Connection conn, Statement st, ResultSet rs) {
       closeResultSet(rs);
       closeStatement(st);
       closeConn(conn);
   }

   //释放连接
   public static void closeConn(Connection conn) {
       if (conn != null) {
           try {
               //释放连接
               conn.close();

               //和当前线程解绑
               threadLocal.remove();
           } catch (SQLException e) {
               e.printStackTrace();
           }

           conn = null;
       }
   }

   //释放语句执行者
   public static void closeStatement(Statement st) {
       if (st != null) {
           try {
               st.close();
           } catch (SQLException e) {
               e.printStackTrace();
           }

           st = null;
       }
   }

   //释放结果集
   public static void closeResultSet(ResultSet rs) {
       if (rs != null) {
           try {
               rs.close();
           } catch (SQLException e) {
               e.printStackTrace();
           }

           rs = null;
       }
   }
}

//JavaBean类
public class Worker {
   //成员变量
   private int id;
   private String name;
   private int age;

   public int getId() {
       return id;
   }

   public void setId(int id) {
       this.id = id;
   }

   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   public int getAge() {
       return age;
   }

   public void setAge(int age) {
       this.age = age;
   }

   @Override
   public String toString() {
      return "Worker{" +
               "id=" + id +
               ", name='" + name + '\'' +
               ", age=" + age +
               '}';
   }
}

十二. 错误例子

  • ... for the right syntax to use near '?,?,?)' at line 1:执行动态SQL语句时,executeUpdate()方法传入了参数。

  • Data truncation: Truncated incorrect DOUBLE value:update语句中的set中出现了and关键字,而应该使用逗号,代替之

  • cannor create query select * from student Parameters: []:它必须是一个公开类,在Android Studio中需要新建一个文件来表示JavaBean类。必须要有setter/getter方法,不然获得JavaBean对象没有数据。

参考文章:

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容