JDBC

不管现实多么惨不忍睹,都要持之以恒地相信,这只是黎明前短暂的黑暗而已。不要惶恐眼前的难关迈不过去,不要担心此刻的付出没有回报,别再花时间等待天降好运。真诚做人,努力做事!你想要的,岁月都会给你。JDBC,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

2.JDBC
2.1.概念
  • Java DataBase Connectivity Java 数据库连接, Java语言操作数据库

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

2.2.使用步骤
  1. 导入驱动jar包 mysql-connector-java-5.1.37-bin.jar
    • 复制mysql-connector-java-5.1.37-bin.jar到项目的libs目录下
    • 右键–>Add As Library
  2. 注册驱动
  3. 获取数据库连接对象 Connection
  4. 定义sql语句
  5. 获取执行sql语句的对象 Statement
  6. 执行sql,接受返回结果
  7. 处理结果
  8. 释放资源
  • Example
//1.导入驱动jar包,复制jar文件到指定工程目录下
//2.注册驱动
    Class.forName("com.mysql.jdbc.Driver");
//3.获取数据库连接对象
    Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db3", "username", "password");
//4.定义sql语句
    String sql = "update account set balance = 500 where id = 1";
//5.获取执行sql的对象 Statement
    Statement stmt = conn.createStatement();
//6.执行sql
    int count = stmt.executeUpdate(sql);
//7.处理结果
    System.out.println(count);
//8.释放资源
    stmt.close();
    conn.close();
2.3.对象详解
2.3.1.DriverManager:驱动管理对象
  1. 注册驱动:告诉程序该使用哪一个数据库驱动jar
    • static void registerDriver(Driver driver) :注册与给定的驱动程序 DriverManager 。
    • 写代码使用: Class.forName(“com.mysql.jdbc.Driver”);
    • 通过查看源码发现:在com.mysql.jdbc.Driver类中存在静态代码块
static {
    try {
        java.sql.DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {
        throw new RuntimeException("Can't register driver!");
    }
}
//注意:mysql5之后的驱动jar包可以省略注册驱动的步骤。
  1. 获取数据库连接:
    • 方法:static Connection getConnection(String url, String user, String password)
    • 参数:
      • url:指定连接的路径
      • 语法:jdbc:mysql://ip地址(域名):端口号/数据库名称,例如:jdbc:mysql://localhost:3306/db3

        如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称

      • user:用户名 password:密码
2.3.2.Connection:数据库连接对象
  1. 获取执行sql 的对象
    • Statement createStatement()
    • PreparedStatement prepareStatement(String sql)
  2. 管理事务:
    • 开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务
    • 提交事务:commit()
    • 回滚事务:rollback()
2.3.3.Statement:执行sql的对象
  1. 执行sql
    1. boolean execute(String sql) :可以执行任意的sql 了解
    2. int executeUpdate(String sql) :执行DML(insert、update、delete)语句、DDL(create,alter、drop)语句
      • 返回值:影响的行数,可以通过这个影响的行数判断DML语句是否执行成功 返回值>0的则执行成功,反之,则失败。
    3. ResultSet executeQuery(String sql) :执行DQL(select)语句
  • Example:
1. account表 添加一条记录
2. account表 修改记录
3. account表 删除一条记录

    Statement stmt = null;
    Connection conn = null;
    try {
        //1. 注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2. 定义sql
        String sql = "insert into account values(null,'王五',3000)";
        //3.获取Connection对象
        conn = DriverManager.getConnection("jdbc:mysql:///db3", "root", "root");
        //4.获取执行sql的对象 Statement
        stmt = conn.createStatement();
        //5.执行sql
        int count = stmt.executeUpdate(sql);//影响的行数
        //6.处理结果
        System.out.println(count);

        if(count > 0){
            System.out.println("添加成功!");
        }else{
            System.out.println("添加失败!");
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (SQLException e) {
        e.printStackTrace();
    }finally {
        //stmt.close();
    
    //7. 释放资源
    //避免空指针异常
    if(stmt != null){
        try {
        	stmt.close();
        } catch (SQLException e) {
        	e.printStackTrace();
        }
    }
    if(conn != null){
    	try {
        	conn.close();
        } catch (SQLException e) {
        	e.printStackTrace();
        }
    }
}
2.3.4.ResultSet:结果集对象,封装查询结果
  • boolean next(): 游标向下移动一行,判断当前行是否是最后一行末尾(是否有数据),如果是,则返回false,如果不是则返回true

  • resultSet.next(); 这个API就类似于我们的Iterator里面维护的游标,执行一次next() 方法,游标就移动一个位置

  • getXxx(参数):获取数据

    • Xxx:代表数据类型 如: int getInt() , String getString()
    • 参数:
      1. int:代表列的编号,从1开始 如: getString(1)
      2. String:代表列名称。 如: getDouble(“balance”)
    • 注意:
      • 使用步骤:
        1. 游标向下移动一行
        2. 判断是否有数据
        3. 获取数据
       //循环判断游标是否是最后一行末尾。
      while(rs.next()){
          //获取数据
          int id = rs.getInt(1);
          String name = rs.getString("name");
          double balance = rs.getDouble(3);
          System.out.println(id + "---" + name + "---" + balance);
      }    
      
  • 常用方法:

    • resultSet.getInt(String columnName);
    • resultSet.getString(String columnName);
    • resultSet.Previous() 移动到前一行
    • resultSet.beforeFirst() 移动到最前面
    • resultSet.afterLast(); 移动到最后面
2.3.5.PreparedStatement:执行sql的对象
  1. SQL注入问题:在拼接sql时,有一些sql的特殊关键字参与字符串的拼接。会造成安全性问题
    1. 输入用户随便,输入密码:a’ or ‘a’ = ‘a
    2. sql:select * from user where username = ‘fhdsjkf’ and password = ‘a’ or ‘a’ = ‘a’
  2. 解决sql注入问题:使用PreparedStatement对象来解决
  3. 预编译的SQL:参数使用?作为占位符
  4. 步骤:
    1. 导入驱动jar包 mysql-connector-java-5.1.37-bin.jar
    2. 注册驱动
    3. 获取数据库连接对象 Connection
    4. 定义sql
      • 注意:sql的参数使用?作为占位符。 如:select * from user where username = ? and password = ?;
    5. 获取执行sql语句的对象 PreparedStatement :Connection.prepareStatement(String sql) 获取PrepareStatement对象
    6. 给?赋值:
      • 方法: setXxx(参数1,参数2)
        • 参数1:?的位置编号 从1 开始
        • 参数2:?的值
    7. 执行sql,接受返回结果,不需要传递sql语句
    8. 处理结果
    9. 释放资源
  5. 注意:后期都会使用PreparedStatement来完成增删改查的所有操作
    1. 可以防止SQL注入
    2. 效率更高
public void testPrepareStatementQuestion() throws SQLException {

    // Statement statement = connection.createStatement();
    // ? 表示占位符
    // 这一句话,会对我们的sql语句去进行预编译,后续我们只需要把 ?填充进来即可,填充进来的都是值,不会是我们的关键字
    PreparedStatement pst = connection.prepareStatement("select * from user where username = ? and password = ?");

    //前面的数字表示第x个问号,在这里1表示第一个?,后面的表示第x个?所替换的值
    pst.setString(1,"lanzhao");
    pst.setString(2,"'lanzhao123' or 1=1 ");

    // 执行sql语句,获取结果
    ResultSet resultSet = pst.executeQuery();

    while(resultSet.next()) {

        String username = resultSet.getString("username");
        String password = resultSet.getString("password");
        String gender = resultSet.getString("gender");
        int id = resultSet.getInt("id");

        System.out.println("id : " + id + ", username : " + username + ", password:" + password + ", gender:" + gender);
    }
}
// 结论,我们通过执行测试,发现并没有找到对应的用户,说明我们这里通过这种预编译占位的方式,可以解决数据库的注入问题
2.4.JDBCUtils
  • 目的:简化书写
    1. 注册驱动也抽取
    2. 抽取一个方法获取连接对象
      • 需求:不想传递参数(麻烦),还得保证工具类的通用性。
      • 解决:配置文件jdbc.properties
        url=jdbc:mysql://localhost:3306/数据库名称?useSSL=false&characterEncoding=utf-8
        user=账号
        password=密码
        dirver=com.mysql.jdbc.Driver
        
  1. 抽取一个方法释放资源
public class JDBCUtils {
    private static String url;
    private static String user;
    private static String password;
    private static String driver;
    /**
     * 文件的读取,只需要读取一次即可拿到这些值。使用静态代码块
     */
    static{
        //读取资源文件,获取值。

        try {
            //1. 创建Properties集合类。
            Properties pro = new Properties();

            //获取src路径下的文件的方式--->ClassLoader 类加载器
            ClassLoader classLoader = JDBCUtils.class.getClassLoader();
            URL res  = classLoader.getResource("jdbc.properties");
            String path = res.getPath();
            System.out.println(path);// D:/IdeaProjects/itcast/out/production/day04_jdbc/jdbc.properties
            //2. 加载文件
           // pro.load(new FileReader("D:\\IdeaProjects\\itcast\\day04_jdbc\\src\\jdbc.properties"));
            pro.load(new FileReader(path));

            //3. 获取数据,赋值
            url = pro.getProperty("url");
            user = pro.getProperty("user");
            password = pro.getProperty("password");
            driver = pro.getProperty("driver");
            
            //4. 注册驱动,其实就是使类加载,类的全路径名已经写在了properties里
            Class.forName(driver);
            
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
     /**
     * 获取连接
     * @return 连接对象
     */
    public static Connection getConnection() throws SQLException {

        return DriverManager.getConnection(url, user, password);
    }

    /**
     * 释放资源
     * @param stmt
     * @param conn
     */
    public static void close(Statement stmt,Connection conn){
        if( stmt != null){
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if( conn != null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
     * 释放资源
     * @param stmt
     * @param conn
     */
    public static void close(ResultSet rs,Statement stmt, Connection conn){
        if( rs != null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if( stmt != null){
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if( conn != null){
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 需求练习
* 需求:
    1. 通过键盘录入用户名和密码
    2. 判断用户是否登录成功
    * select * from user where username = "" and password = "";
* 如果这个sql有查询结果,则成功,反之,则失败

//1. 创建数据库表 user
CREATE TABLE USER(
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(32),
    PASSWORD VARCHAR(32)
);

INSERT INTO USER VALUES(NULL,'zhangsan','123');
INSERT INTO USER VALUES(NULL,'lisi','234');

2. 代码实现:
    public class JDBCPractice {

        public static void main(String[] args) {
            //1.键盘录入,接受用户名和密码
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入用户名:");
            String username = sc.nextLine();
            System.out.println("请输入密码:");
            String password = sc.nextLine();
            //2.调用方法
            boolean flag = new JDBCUtils().login(username, password);
            //3.判断结果,输出不同语句
            if(flag){
                //登录成功
                System.out.println("登录成功!");
            }else{
                System.out.println("用户名或密码错误!");
            }
        }

        /**
		 * 登录方法
		 */
        public boolean login(String username ,String password){
            if(username == null || password == null){
                return false;
            }
            //连接数据库判断是否登录成功
            Connection conn = null;
            Statement stmt =  null;
            ResultSet rs = null;
            //1.获取连接
            try {
                conn =  JDBCUtils.getConnection();
                //2.定义sql
                String sql = "select * from user where username = '"+username+"' and password = '"+password+"' ";
                //3.获取执行sql的对象
                stmt = conn.createStatement();
                //4.执行查询
                rs = stmt.executeQuery(sql);
                //5.判断
                /* if(rs.next()){//如果有下一行,则返回true
			                return true;
			            }else{
			                return false;
			            }*/
                return rs.next();//如果有下一行,则返回true

            } catch (SQLException e) {
                e.printStackTrace();
            }finally {
                JDBCUtils.close(rs,stmt,conn);
            }
            return false;
        }
    }
2.5.JDBC批处理
  • 业务场景:当需要向数据库发送一批SQL语句执行时,应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,以提升执行效率。
  • 默认情况下Mysql的批处理仍然是一条一条执行,需要在url后面添加rewriteBatchedStatements=true参数
  • 实现批处理有两种方式
    • 第一种方式:
      • Statement.addBatch(sql)
      • 执行批处理SQL语句
        • executeBatch()方法:执行批处理命令
        • clearBatch()方法:清除批处理命令
      • 优点:可以向数据库发送多条不同的SQL语句。
      • 缺点:
        • SQL语句没有预编译。
        • 当向数据库发送多条语句相同,但仅参数不同的SQL语句时,需重复写上很多条SQL语句
    // Statement批处理
    public void testInsertBatch() throws SQLException {
        long startTime = System.currentTimeMillis();
        // 这里这个batch想当是一个小推车,帮助我们更好的搬砖
        for (int i = 0; i < 2000; i++) {
            String username = "兰钊" + i;
            statement.addBatch("insert into user values (null,'"+username+"','cskaoyan','male')");
    
        }
    
        statement.executeBatch();
    
        statement.close();
    
        long endTime = System.currentTimeMillis();
    
        System.out.println("statement批处理:" + (endTime -startTime));
    }
    
    • 实现批处理的第二种方式:
      • 采用PreparedStatement.addBatch()实现批处理
      • 优点:与数据库通信次数在批量操作时,PreparedStatment的通信次数远少于Statment。
      • 缺点:只能应用在SQL语句相同,但参数不同的批处理中。因此此种形式的批处理经常用于在同一个表中批量插入数据,或批量更新表的数据。
    // prepareStatement 进行批处理
    public void testPrepareStatementBatch() throws SQLException {
    
        PreparedStatement preparedStatement = connection.prepareStatement("insert into user values (null,?,?,?)");
        long startTime = System.currentTimeMillis();
        // 批量放入
        for (int i = 0; i < 2000; i++) {
            String username = "李白龙" + i;
            preparedStatement.setString(1,username);
            preparedStatement.setString(2,"cskaoyan111");
            preparedStatement.setString(3,"male");
    
            preparedStatement.addBatch();
    
        }
        // 批处理执行
        int[] ints = preparedStatement.executeBatch();
    
        long endTime = System.currentTimeMillis();
    
        System.out.println("prepareStatement批处理:" + (endTime -startTime));
    }
    
2.6.JDBC控制事务
2.6.1.事务
  • 一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。
  • 操作:
    1. 开启事务
    2. 提交事务
    3. 回滚事务
2.6.2.使用Connection对象来管理事务
  • 开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务
    • 在执行sql之前开启事务
  • 提交事务:commit()
    • 当所有sql都执行完提交事务
  • 回滚事务:rollback()
    • 在catch中回滚事务
2.6.3.Example
public class JDBCExample {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pstmt1 = null;
        PreparedStatement pstmt2 = null;

        try {
            //1.获取连接
            conn = JDBCUtils.getConnection();
            //开启事务
            conn.setAutoCommit(false);

            //2.定义sql
            //2.1 张三 - 500
            String sql1 = "update account set balance = balance - ? where id = ?";
            //2.2 李四 + 500
            String sql2 = "update account set balance = balance + ? where id = ?";
            //3.获取执行sql对象
            pstmt1 = conn.prepareStatement(sql1);
            pstmt2 = conn.prepareStatement(sql2);
            //4. 设置参数
            pstmt1.setDouble(1,500);
            pstmt1.setInt(2,1);

            pstmt2.setDouble(1,500);
            pstmt2.setInt(2,2);
            //5.执行sql
            pstmt1.executeUpdate();
            // 手动制造异常
            int i = 3/0;

            pstmt2.executeUpdate();
            //提交事务
            conn.commit();
        } catch (Exception e) {
            //事务回滚
            try {
                if(conn != null) {
                    conn.rollback();
                }
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        }finally {
            JDBCUtils.close(pstmt1,conn);
            JDBCUtils.close(pstmt2,null);
        }
    }
}
2.7.DBUtils框架
2.7.1.DbUtils类
  • 帮助程序员解析结果,并封装为对象或集合
  • 提供如关闭连接、装载JDBC驱动程序等常规工作的工具类,里面的所有方法都是静态的
  • 主要方法
    • public static void close(…) throws java.sql.SQLException:
      • DbUtils类提供了三个重载的关闭方法。这些方法检查所提供的参数是不是NULL,如果不是的话,它就关闭Connection、Statement和ResultSet
    • public static void closeQuietly(…):
      • 这一类方法不仅能在Connection、Statement和ResultSet为NULL情况下避免关闭,还能隐藏一些在程序中抛出的SQLEeception。
    • public static void commitAndCloseQuietly(Connection conn):
      • 用来提交连接,然后关闭连接,并且在关闭连接时不抛出SQL异常。
    • public static boolean loadDriver(java.lang.String driverClassName):
      • 这一方装载并注册JDBC驱动程序,如果成功就返回true。使用该方法,你不需要捕捉这个异常ClassNotFoundException。
2.7.2.QueryRunner类
  • 简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量
  • 构造方法
    • public QueryRunner()
    • public QueryRunner(DataSource ds)
  • 主要方法
    • public Object query(Connection conn, String sql, Object[] params, ResultSetHandler rsh) throws SQLException
      • 执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。
      • 该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭。
    • public Object query(String sql, Object[] params, ResultSetHandler rsh) throws SQLException
      • 几乎与第一种方法一样;唯一的不同在于它不将数据库连接提供给方法,并且它是从提供给构造方法的数据源(DataSource) 或使用的setDataSource 方法中重新获得 Connection。
    • public Object query(Connection conn, String sql, ResultSetHandler rsh) throws SQLException
      • 执行一个不需要置换参数的查询操作。
    • public int update(Connection conn, String sql, Object[] params) throws SQLException
      • 用来执行一个更新(插入、更新或删除)操作。
    • public int update(Connection conn, String sql) throws SQLException
      • 用来执行一个不需要置换参数的更新操作。
    // 查询
    QueryRunner queryRunner = new QueryRunner(dataSource);
    queryRunner.query(sql,resultSetHandler);  // 这种方式DBUtils会帮助我们关闭连接
    
    queryRunner.query(sql,resultSetHandler,param...);  // 这种方式DBUtils会帮助我们关闭连接
    
    // 查询-传递进去连接
    QueryRunner queryRunner = new QueryRunner();  // 这个地方就不传递数据源
    queryRunner.query(connection,sql,resultSetHandler);  // 这种方式DBUtils不会帮助我们关闭连接
    
    queryRunner.query(connection,sql,resultSetHandler,param...);
    
    // 修改、删除、增加都是使用update方法
    QueryRunner queryRunner = new QueryRunner(dataSource);
    queryRunner.update(sql,params...);
    
    queryRunner.update(connection,sql,params...);
    
2.7.3.ResultSetHandler接口
  • 该接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。
  • ResultSetHandler 接口提供了一个单独的方法:Object handle (java.sql.ResultSet .rs)。
  • ResultSetHandler底层是通过反射来实现的封装,User对象和数据库中的名称一定要相同,如果不相同则无法封装进集合里。
  • 实现类
    • ArrayHandler:把结果集中的第一行数据转成对象数组。
    • ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
    • BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
    • BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
    • ColumnListHandler:将结果集中某一列的数据存放到List中。
    • KeyedHandler(name):将结果集中的每一行数据都封装到一个Map<列名,列值>里,再把这些map再存到一个map里,其key为指定的key。
    • MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
    • MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
    • ScarlarHandler:将单个值封装,可以用来统计聚合函数count(),max(),min(),avg()等方法返回的值
  • BeanListHandler类
    • 帮助我们把查询的结果转化为一个List
    @Test
    public void testQueryBeanListHandler() throws SQLException {
        // 创建QueryRunner对象
        QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
        // 我们要执行sql
        String sql = "select * from user";
        List<User> userList = queryRunner.query(sql, new BeanListHandler<User>(User.class));
        System.out.println(userList);
    }
    
  • BeanHandler类
    • 帮助我们把查询的结果转化为一个Bean,注意这里是指我们查询结果的第一条记录
    @Test
    public void testSelectOne() throws SQLException {
        QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
        // 我们要执行sql
        String sql = "select * from user where id = ?";
        //1这个参数相当于上边的?的值
        User user = queryRunner.query(sql, new BeanHandler<User>(User.class),1);
        System.out.println(user);
    }
    
  • ScalarHandler类
    • 直接返回我们查询的结果(AVG、SUM、COUNT、MAX、MIN)
    @Test
    public void testSelectCount() throws SQLException {
        QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
        String sql = "select count(1) from user";
        Object query = queryRunner.query(sql, new ScalarHandler<>());
        System.out.println(query);
    }
    
  • ColunmnListHandler类
    • 查询单列的一个结果,封装到一个List里
    // 查询单列 ColumnListHandler
    @Test
    public void testSelectNameList() throws SQLException {
        QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
        String sql = "select username from user";
        List<String> strList = queryRunner.query(sql, new ColumnListHandler<String>());
        System.out.println(strList);
    }
    
2.7.4.DButils使用
//创建一个User类,封装成JavaBea的标准形式,并对应数据库的字段名称
public class User {

    private Integer id;
    private String username;
    private String password;
    private String gender;

    //省略所必须要的get、set方法
}


//使用已经写好的Druid连接池
public class DruidUtils {

    private static DataSource dataSource;

    static {
        // 加载配置文件
        Properties properties = new Properties();
        ClassLoader classLoader = DruidUtils.class.getClassLoader();
        InputStream stream = classLoader.getResourceAsStream("druid.properties");

        try {
            properties.load(stream);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 创建一个Druid数据源工厂对象
        DruidDataSourceFactory dataSourceFactory = new DruidDataSourceFactory();
        try {
            // 通过工厂创建一个数据源并且去赋值
            dataSource = dataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 获取连接
    public static Connection getConnection(){

        Connection connection = null;
        try {
            connection = dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return connection;
    }

    // 获取数据源
    public static DataSource getDataSource(){
        return dataSource;
    }
}



//测试类
public class DButilsTest {

    // DBUtils
    @Test
    public void testDBtils(){
//        DbUtils.close();
//        DbUtils.commitAndClose();
    }

    @Test
    public void testQueryBasic(){

        ArrayList<User> users = new ArrayList<>();
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            connection = DruidUtils.getConnection();
            preparedStatement = connection.prepareStatement("select * from user");

            resultSet = preparedStatement.executeQuery();

            while (resultSet.next()) {

                User user = new User();
                user.setId(resultSet.getInt("id"));
                user.setUsername(resultSet.getString("username"));
                user.setPassword(resultSet.getString("password"));
                user.setGender(resultSet.getString("gender"));
                users.add(user);
            }
            // 输出
            System.out.println(users);

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

        }finally {
            JDBCUtils.releaseSources(connection,preparedStatement,resultSet);
        }
    }
// 关于传不传数据源,
// 都是可以的,假如我们就是做简单的增删改查,就传递数据源
// 什么情况我们采用QueryRunner传递数据源的构造方法呢?
    // 假如我们在使用事务的时候,就不能传递数据源了,因为事务要使用同一个connection对象

    // QueryRunner
    @Test
    public void testQueryRunnerBasic() throws SQLException {
        // 创建一个QueryRunner
        QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
        // 调用方法
        int affectedRows = queryRunner.update("delete from user where id = ?", 404);
        System.out.println(affectedRows);
    }

    // QueryRunner  delete
    @Test
    public void testQueryRunnerBasic2() throws SQLException {
        ArrayList<User> users = new ArrayList<>();
        // 创建一个QueryRunner
        QueryRunner queryRunner = new QueryRunner();
        Connection connection = DruidUtils.getConnection();
        // 调用方法
        int affectedRows = queryRunner.update(connection,"delete from user where id = ?", 4005);
        System.out.println(affectedRows);

    }
    // QueryRunner update
    @Test
    public void testQueryRunnerUpdate() throws SQLException {
        // 创建一个QueryRunner
        QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
        // 调用方法
        int affectedRows = queryRunner.update("update user set username = ? where id = ?", "lanzhao520",2);
        System.out.println(affectedRows);
    }

    // QueryRunner insert
    @Test
    public void testQueryRunnerInsert() throws SQLException {
        // 创建一个QueryRunner
        QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
        String sql = "insert into user values (?,?,?,?)";
        // 调用方法
        int affectedRows = queryRunner.update(sql, 3,"小钻风","大王叫我来巡山","gender");
        System.out.println(affectedRows);
    }

    // 关闭连接的问题:
        // 如果采用的这种方式去创建QueryRunner: new QueryRunner(DruidUtils.getDataSource()), 那么会帮我们关闭连接
        // 如果采用无参的方式去创建QueryRunner,那么就不会帮我们关闭连接
    @Test
    public void testQueryList1() throws SQLException {
        // 创建QueryRunner对象
        QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
        // 我们要执行sql
        String sql = "select * from user";
        List<User> userList = queryRunner.query(sql, new MyResultSetHandler());
        System.out.println(userList);
    }
	// 这种方式仍然不好用,因为还是需要我们自己解析
    
    
    // 使用已经封装好的实现类
    // 查询一个List
    @Test
    public void testQueryBeanListHandler() throws SQLException {
        // 创建QueryRunner对象
        QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
        // 我们要执行sql
        String sql = "select * from user";
        List<User> userList = queryRunner.query(sql, new BeanListHandler<User>(User.class));
        System.out.println(userList);
    }

    // 查询单个bean
    @Test
    public void testSelectOne() throws SQLException {
        QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
        // 我们要执行sql
        String sql = "select * from user where id = ?";
        User user = queryRunner.query(sql, new BeanHandler<User>(User.class),1);
        System.out.println(user);
    }

    // 查询聚合函数的值,例如count,avg,max
    @Test
    public void testSelectCount() throws SQLException {
        QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
        String sql = "select count(1) from user";
        Object query = queryRunner.query(sql, new ScalarHandler<>());
        System.out.println(query);
    }
    // 查询单列 ColumnListHandler
    @Test
    public void testSelectNameList() throws SQLException {
        QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
        String sql = "select username from user";
        List<String> strList = queryRunner.query(sql, new ColumnListHandler<String>());
        System.out.println(strList);
    }

    // 查询单个bean,测试反射猜想
    @Test
    public void testSelectOne2() throws SQLException {
        QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
        // 我们要执行sql
        String sql = "select * from user where id = ?";
        UserVO user = queryRunner.query(sql, new BeanHandler<UserVO>(UserVO.class),1);
        System.out.println(user);
    }


    // 实现批处理功能
    @Test
    public void testBatch() throws SQLException {

        QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());

        String sql = "insert into user values (?,?,?,?)";
        Object[][] params = new Object[3][];
        params[0] = new Object[]{4,"金角大王","我叫你一声你敢答应吗","male"};
        params[1] = new Object[]{5,"铁扇公主","牛夫人","female"};
        params[2] = new Object[]{6,"女儿国国王","唐长老,留下来","female"};

        // 这个batchRet数组对应的是每一条sql语句影响的行数
        int[] batchRet = queryRunner.batch(sql, params);

        for (int affectedRows : batchRet) {
            System.out.println("影响的行数:" + affectedRows);
        }
    }
}




//使用DBUtils实现事务
public class TranferTest {

	//这里没有使用事务,如果中途失败会导致账户损失
    @Test
    public void testTransfer1() {

        QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
        Connection connection = DruidUtils.getConnection();

        try {
            connection.setAutoCommit(false);

            String sql = "update account set money = money + ? where name = ?";
            // 扣兰钊的钱 -200
            int affectedRows = queryRunner.update(sql, -200, "兰钊");
            if (affectedRows < 1) {
                System.out.println("扣钱失败!");
                return;
            }
            // 给雪茄加钱
            int affectedRows2 = queryRunner.update(sql, 200, "雪茄");
            if (affectedRows2 < 1) {
                System.out.println("加钱失败!!");
                connection.rollback();
            }

            connection.commit();
        }catch (Exception ex) {
            ex.printStackTrace();
            try {
                connection.rollback();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }


    }

	
    //这里的session不是同一个对象,也没有实现事务
    @Test
    public void testTransfer2() {
		
        QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
        Connection connection = DruidUtils.getConnection();

        try {
            connection.setAutoCommit(false);

            String sql = "update account set money = money + ? where name = ?";
            // 扣兰钊的钱 -200
            // 用的不是同一个Connection对象
            int affectedRows = queryRunner.update(sql, -200, "兰钊");
            if (affectedRows < 1) {
                System.out.println("扣钱失败!");
                return;
            }

            int i = 1/0;  //除0异常,按理说应该回滚事务,但是这里并不能控制事务
            // 给雪茄加钱
            // 用的不是同一个Connection对象
            int affectedRows2 = queryRunner.update(sql, 200, "雪茄");
            if (affectedRows2 < 1) {
                System.out.println("加钱失败!!");
                connection.rollback();
            }

            connection.commit();
        }catch (Exception ex) {
            ex.printStackTrace();
            try {
                connection.rollback();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    
    @Test
    public void testTransfer3() {

        QueryRunner queryRunner = new QueryRunner();
        //创建一个Connection对象
        Connection connection = DruidUtils.getConnection();

        try {
            connection.setAutoCommit(false);

            String sql = "update account set money = money + ? where name = ?";
            // 扣兰钊的钱 -200
            // 传入同一个Connection对象
            int affectedRows = queryRunner.update(connection,sql, -200, "兰钊");
            if (affectedRows < 1) {
                System.out.println("扣钱失败!");
                return;
            }

            int i = 1/0;
            // 给雪茄加钱
            // 传入同一个Connection对象
            int affectedRows2 = queryRunner.update(connection,sql, 200, "雪茄");
            if (affectedRows2 < 1) {
                System.out.println("加钱失败!!");
                connection.rollback();
            }

            connection.commit();
        }catch (Exception ex) {
            ex.printStackTrace();
            try {
                connection.rollback();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }finally {
            // 关闭连接
            DbUtils.closeQuietly(connection);
        }
    }
}
2.7.数据库连接池
2.7.1.概念
  • 其实就是一个容器(集合),存放数据库连接的容器。当系统初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器。
2.7.2.好处
  • 节约资源
  • 用户访问高效
2.7.3.实现:
  • 标准接口:java提供了一个数据库标准接口DataSource,javax.sql包下的,需实现这个接口标准化

  • 获取连接:getConnection()

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

    connection是个JavaEE标准库中JDBC提供的,我们能实例化connection对象,是因为mysql提供的驱动的getConnection()方法返回了该接口的实现类,也就是说,我们定义的connection对象,本质是多态实现的。为了实现统一,我们不需要具体了解各大数据库厂商是如何实现这套接口的,只需要使用connection多态的统一定义方式就可以了。

  • 一般不去实现它,数据库厂商来实现

  • DBCP:数据库连接池技术

  • C3P0:数据库连接池技术

  • Druid:数据库连接池实现技术,由阿里提供

2.7.4.DBCP
  • 导入jar包,Maven项目导包配置如下
    <!--DBCP-->
    <dependency>
        <groupId>commons-dbcp</groupId>
        <artifactId>commons-dbcp</artifactId>
        <version>1.4</version>
    </dependency>
    
    <dependency>
        <groupId>commons-pool</groupId>
        <artifactId>commons-pool</artifactId>
        <version>1.6</version>
    </dependency>
    
  • 建立一个dbcp.properties文件,放在resources目录下
    #连接设置
    driverClassName=com.mysql.jdbc.Driver
    url=jdbc:mysql://localhost:3306/28_jdbc
    username=root
    password=123456
    #<!-- 初始化连接 -->
    initialSize=10
    
    #最大连接数量
    maxActive=50
    
    #<!-- 最大空闲连接 -->
    maxIdle=20
    
    #<!-- 最小空闲连接 -->
    minIdle=5
    
    #<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
    maxWait=60000
    #JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]
    #注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
    connectionProperties=useUnicode=true;characterEncoding=utf-8;useSSL=false
    
    #指定由连接池所创建的连接的自动提交(auto-commit)状态。
    defaultAutoCommit=true
    
    #driver default 指定由连接池所创建的连接的只读(read-only)状态。
    #如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
    defaultReadOnly=
    
    #driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
    #可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
    defaultTransactionIsolation=REPEATABLE_READ
    
  • 创建DBCPUtils对象
    public class DBCPUtils {
    
        // 声明一个数据源对象
        private static DataSource dataSource;
    
        static {
    
            // 加载配置文件
            Properties properties = new Properties();
            ClassLoader classLoader = DBCPUtils.class.getClassLoader();
            InputStream stream = classLoader.getResourceAsStream("dbcp.properties");
            try {
                properties.load(stream);
            } catch (IOException e) {
                e.printStackTrace();
            }
    		// 涉及到了一个设计模式:工厂模式
            // 创建一个数据源工厂对象
            BasicDataSourceFactory basicDataSourceFactory = new BasicDataSourceFactory();
    
            try {
                // 给Datasource赋值
                dataSource = basicDataSourceFactory.createDataSource(properties);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    
        // 写一个方法 获取连接
        public static Connection getConnection(){
            Connection connection = null;
            try {
                connection = dataSource.getConnection();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return connection;
        }
    }
    
2.7.5.C3P0
  • 导入jar包 (两个) c3p0-0.9.5.2.jar 和 mchange-commons-java-0.2.12.jar ,不要忘记导入数据库驱动jar包
  • Maven项目下导包配置如下
    <!--c3p0-->
    <dependency>
        <groupId>com.mchange</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.5.4</version>
    </dependency>
    
  • 需要说明的是,这个配置文件的名字必须是 c3p0-config.xml,并且必须放在Resources目录下
    <?xml version="1.0" encoding="UTF-8"?>
    <c3p0-config>
        <default-config>
            <property name="driverClass">com.mysql.jdbc.Driver</property>
            <property name="jdbcUrl">jdbc:mysql://localhost:3306/28_jdbc</property>
            <property name="user">root</property>
            <property name="password">123456</property>
    
            <property name="acquireIncrement">5</property>
            <property name="initialPoolSize">10</property>
            <property name="minPoolSize">5</property>
            <property name="maxPoolSize">20</property>
        </default-config>
    </c3p0-config>
    
  • 创建C3p0Utils对象
    public class C3p0Utils {
    
        // 首先声明一个数据源对象
        private static DataSource dataSource;
    
        static {
    
            // 给Datasource对象去赋值
            dataSource = new ComboPooledDataSource();
        }
    
        // 获取连接
        public static Connection getConnection(){
    
            Connection connection = null;
            try {
                connection = dataSource.getConnection();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return connection;
    
        }
    }
    
2.7.6.Druid
  1. 导入jar包 druid-1.0.9.jar
    • maven项目导包配置如下
      <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
      <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid</artifactId>
          <version>1.1.19</version>
      </dependency>
      
  2. 定义配置文件:
    • 建立一个dbcp.properties文件,放在我们的resources目录下
      driverClassName=com.mysql.jdbc.Driver
      url=jdbc:mysql://localhost:3306/数据库名称
      username=root
      password=密码
      
  3. 简略步骤如下
    //1.创建数据库连接池对象
    DataSource ds  = new ComboPooledDataSource();
    //2. 获取连接对象
    Connection conn = ds.getConnection();
    //3.加载配置文件
    Properties pro = new Properties();
    InputStream is = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
    pro.load(is);
    //4.获取连接池对象
    DataSource ds = DruidDataSourceFactory.createDataSource(pro);
    //5.获取连接
    Connection conn = ds.getConnection();
    
  4. 创建DruidUtils对象
    public class DruidUtils {
    
        private static DataSource dataSource;
    
        static {
            // 加载配置文件
            Properties properties = new Properties();
            ClassLoader classLoader = DruidUtils.class.getClassLoader();
            InputStream stream = classLoader.getResourceAsStream("druid.properties");
    
            try {
                properties.load(stream);
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            // 创建一个Druid数据源工厂对象
            DruidDataSourceFactory dataSourceFactory = new DruidDataSourceFactory();
            try {
                // 通过工厂创建一个数据源并且去赋值
                dataSource = dataSourceFactory.createDataSource(properties);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        // 获取连接
        public static Connection getConnection(){
    
            Connection connection = null;
            try {
                connection = dataSource.getConnection();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return connection;
        }
    }
    
2.8.Spring JDBC
  • Spring框架对JDBC的简单封装。提供了一个JDBCTemplate对象简化JDBC的开发
  • 步骤:
    1. 导入jar包
    2. 创建JdbcTemplate对象。依赖于数据源DataSource
      • JdbcTemplate template = new JdbcTemplate(ds);
  1. 调用JdbcTemplate的方法来完成CRUD的操作
    • update():执行DML语句。增、删、改语句
    • queryForMap():查询结果将结果集封装为map集合,将列名作为key,将值作为value 将这条记录封装为一个map集合

      注意:这个方法查询的结果集长度只能是1

    • queryForList():查询结果将结果集封装为list集合

      注意:将每一条记录封装为一个Map集合,再将Map集合装载到List集合中

    • query():查询结果,将结果封装为JavaBean对象
    • query的参数:RowMapper

      一般我们使用BeanPropertyRowMapper实现类。可以完成数据到JavaBean的自动封装

    • new BeanPropertyRowMapper<类型>(类型.class)
    • queryForObject:查询结果,将结果封装为对象

      一般用于聚合函数的查询

  • Example
/*
需求:
    1. 修改1号数据的 salary 为 10000
    2. 添加一条记录
    3. 删除刚才添加的记录
    4. 查询id为1的记录,将其封装为Map集合
    5. 查询所有记录,将其封装为List
    6. 查询所有记录,将其封装为Emp对象的List集合
    7. 查询总记录数
*/

import cn.itcast.domain.Emp;
import cn.itcast.utils.JDBCUtils;
import org.junit.Test;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import java.sql.Date;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;


public class JdbcTemplate {

    //Junit单元测试,可以让方法独立执行    
    //1. 获取JDBCTemplate对象
    private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());
    
    /**
    * 1. 修改1号数据的 salary 为 10000
    */
    @Test
    public void test1(){
        //2. 定义sql
        String sql = "update emp set salary = 10000 where id = 1001";
        //3. 执行sql
        int count = template.update(sql);
        System.out.println(count);
    }
    
    /**
    * 2. 添加一条记录
    */
    @Test
    public void test2(){
        String sql = "insert into emp(id,ename,dept_id) values(?,?,?)";
        int count = template.update(sql, 1015, "郭靖", 10);
        System.out.println(count);

    }

    /**
    * 3.删除刚才添加的记录
    */
    @Test
    public void test3(){
        String sql = "delete from emp where id = ?";
        int count = template.update(sql, 1015);
        System.out.println(count);
    }

    /**
    * 4.查询id为1001的记录,将其封装为Map集合
    * 注意:这个方法查询的结果集长度只能是1
    */
    @Test
    public void test4(){
        String sql = "select * from emp where id = ? or id = ?";
        Map<String, Object> map = template.queryForMap(sql, 1001,1002);
        System.out.println(map);
        //{id=1001, ename=孙悟空, job_id=4, mgr=1004, joindate=2000-12-17, salary=10000.00, bonus=null, dept_id=20}

    }

    /**
    * 5. 查询所有记录,将其封装为List
    */
    @Test
    public void test5(){
        String sql = "select * from emp";
        List<Map<String, Object>> list = template.queryForList(sql);

        for (Map<String, Object> stringObjectMap : list) {
            System.out.println(stringObjectMap);
        }
    }

    /**
    * 6.1. 查询所有记录,将其封装为Emp对象的List集合
    */
    @Test
    public void test6(){
        String sql = "select * from emp";
        List<Emp> list = template.query(sql, new RowMapper<Emp>() {

            @Override
            public Emp mapRow(ResultSet rs, int i) throws SQLException {
                Emp emp = new Emp();
                int id = rs.getInt("id");
                String ename = rs.getString("ename");
                int job_id = rs.getInt("job_id");
                int mgr = rs.getInt("mgr");
                Date joindate = rs.getDate("joindate");
                double salary = rs.getDouble("salary");
                double bonus = rs.getDouble("bonus");
                int dept_id = rs.getInt("dept_id");

                emp.setId(id);
                emp.setEname(ename);
                emp.setJob_id(job_id);
                emp.setMgr(mgr);
                emp.setJoindate(joindate);
                emp.setSalary(salary);
                emp.setBonus(bonus);
                emp.setDept_id(dept_id);

                return emp;
            }
        });
        for (Emp emp : list) {
            System.out.println(emp);
        }
    }

    /**
    * 6.2. 查询所有记录,将其封装为Emp对象的List集合
    */
    @Test
    public void test6_2(){
        String sql = "select * from emp";
        List<Emp> list = template.query(sql, new BeanPropertyRowMapper<Emp>(Emp.class));
        for (Emp emp : list) {
            System.out.println(emp);
        }
    }

    /**
    * 7. 查询总记录数
    */
    @Test
    public void test7(){
        String sql = "select count(id) from emp";
        Long total = template.queryForObject(sql, Long.class);
        System.out.println(total);
    }
    
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/181065.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!