暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

JDBC核心技术

Geeker工作坊 2021-07-12
250

JDBC概述

- 数据持久化

  1. 持久化(persistence):把数据保存到可掉电式存储设备中以供之后使用。大多数情况下,特别是企业级应用,数据持久化意味着将内存中的数据保存到硬盘上加以”固化”,而持久化的实现过程大多通过各种关系数据库来完成

  2. 持久化的主要应用是将内存中的数据存储在关系型数据库中,当然也可以存储在磁盘文件、XML数据文件中。


    - Java中的数据存储技术

    1. 在Java中,数据库存取技术可分为如下几类:

    • JDBC直接访问数据库

    • JDO (Java Data Object )技术

    • 第三方O/R工具,如Hibernate, Mybatis 等

    1. JDBC是java访问数据库的基石,JDO、Hibernate、MyBatis等只是更好的封装了JDBC。

    - JDBC介绍

    JDBC是SUN公司提供的一套用于数据库操作的接口,Java程序员只需要面向这套接口编程即可。不同的数据库厂商,需要针对这套接口,提供不同实现。不同的实现的集合,即为不同的数据库驱动。--面向接口的编程

    1. JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库,(java.sql,javax.sql)使用这些类库可以以一种标准的方法、方便地访问数据库资源。

    2. JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题。

    3. JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程。

    - JDBC体系结构

    JDBC接口(API)包括两个层次:

    • 面向应用的API:Java API,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)。

    • 面向数据库的API:Java Driver API,供开发商开发数据库驱动程序用。

    - JDBC程序编写步骤

    数据库连接

    实现数据库连接的方式有多种,本次记录使用配置文件方式获取数据连接。使用配置文件好处如下

    1. 实现了代码和数据的分离,如果需要修改配置文件,直接在配置文件中修改,不需要深入代码

    2. 如果修改了配置信息,省去重新编译的过程

      public  void testConnect() throws Exception {
      //1.加载配置文件
            InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
            Properties pros = new Properties();
            pros.load(is);

             //2.读取配置信息
            String user = pros.getProperty("user");
            String password = pros.getProperty("password");
            String url = pros.getProperty("url");
            String driverClass = pros.getProperty("driverClass");


            //3.加载驱动
            Class.forName(driverClass);


            //4.获取连接
            Connection conn = DriverManager.getConnection(url,user,password);
            System.out.println(conn);
      }

      其中,配置文件声明在工程的src目录下: [jdbc.properties]

        user=root
        password=123456
        url=jdbc:mysql://localhost:3306/test
        driverClass=com.mysql.jdbc.Driver

        使用PreparedStatement实现CURD操作

        - 操作和访问数据库

        1. 数据库连接被用于向数据库服务器发送命令和SQL语句,并接受数据库服务器返回的结果。其实一个数据库连接就是一个Socket连接

        2. 在java.sql包有3个接口分别定义了对数据库的调用的不同方式:

          • Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。

          • PrepatedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。

          • CallableStatement:用于执行 SQL 存储过程

        - 使用Statement操作数据表的弊端

        • 问题一:存在拼串操作,繁琐

        • 问题二:存在SQL注入问题

        SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令(如:SELECT user, password FROM user_table WHERE user='a' OR 1 = ' AND password = ' OR '1' = '1') ,从而利用系统的 SQL 引擎完成恶意行为的做法。对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement(从Statement扩展而来) 取代 Statement 就可以了。

        - PreparedStatement的使用

        1. PreparedStatement介绍

        • 可以通过调用 Connection 对象的 preparedStatement(String sql) 方法获取 PreparedStatement 对象

        • PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句

        • PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示,调用 PreparedStatement 对象的 setXxx() 方法来设置这些参数. setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值

        1. 使用PreparedStatement实现增删改的操作

          //通用的增、删、改操作(体现一:增、删、改 ;体现二:针对于不同的表)
          public void update(String sql,Object ... args){
          Connection conn = null;
          PreparedStatement ps = null;
          try {
          //1.获取数据库的连接
          conn = JDBCUtils.getConnection();


          //2.获取PreparedStatement的实例 (或:预编译sql语句)
          ps = conn.prepareStatement(sql);
          //3.填充占位符
          for(int i = 0;i < args.length;i++){
          ps.setObject(i + 1, args[i]);
          }


          //4.执行sql语句
          ps.execute();
          } catch (Exception e) {


          e.printStackTrace();
          }finally{
          //5.关闭资源
          JDBCUtils.closeResource(conn, ps);


          }
          }
          1. 使用PreparedStatement实现查询操作

            public <T> List<T> getForList(Class<T> clazz, String sql, Object ...args){
            Connection conn = null;
            PreparedStatement ps = null;
            ResultSet rs = null;
            List<T> list = null;
            try{
            conn = JDBCUtils.getConnection();
            ps = conn.prepareStatement(sql);
            for(int i=0;i<args.length;i++){
            ps.setObject(i+1,args[i]);
            }
            rs = ps.executeQuery();
            ResultSetMetaData metaData = rs.getMetaData();
            int columnCount = metaData.getColumnCount();
            while(rs.next()){
            T t = clazz.getDeclaredConstructor().newInstance();
            for(int i=1;i<=columnCount;i++){
            String columnLabel = metaData.getColumnLabel(i);
            Object columnValue = rs.getObject(i);
            Field field = clazz.getDeclaredField(columnLabel);
            field.setAccessible(true);
            field.set(t,columnValue);
            }
            if(list == null) list = new ArrayList<T>();
            list.add(t);
            }
            return list;
            }catch (Exception e){
            e.printStackTrace();
            }finally {
            JDBCUtils.closeResource(conn,ps,rs);
            }
            return null;
            }
            /**
            * 返回一个实例
            * @param clazz
            * @param sql
            * @param args
            * @param <T>
            * @return T
            */
            public <T> T getInstance(Class<T> clazz, String sql, Object ...args){
            Connection conn = null;
            PreparedStatement ps = null;
            ResultSet rs = null;
            try{
            conn = JDBCUtils.getConnection();
            ps = conn.prepareStatement(sql);
            for(int i=0;i<args.length;i++){
            ps.setObject(i+1,args[i]);
            }
            rs = ps.executeQuery();
            ResultSetMetaData metaData = rs.getMetaData();
            int columnCount = metaData.getColumnCount();
            if(rs.next()){
            T t = clazz.getDeclaredConstructor().newInstance();
            for(int i=1;i<=columnCount;i++){
            Object columnValue = rs.getObject(i);
            String columnLabel = metaData.getColumnLabel(i);
            Field field = clazz.getDeclaredField(columnLabel);
            field.setAccessible(true);
            field.set(t,columnValue);
            }
            return t;
            }
            }catch (Exception e){
            e.printStackTrace();
            }finally {
            JDBCUtils.closeResource(conn,ps,rs);
            }
            return null;
            }

            查询过程中可以总结为两种编程思想和运用两种技术

            [两种编程思想]

            1. 面向接口编程的思想

            2. ORM编程思想(object relation mapping)

              • 一个数据表对应一个Java类

              • 表中的一条记录对应Java类中的一个字段

              • 表中一个字段对应Java类中的一个属性

            [两种技术]

            1. 使用结果集的元数据:ResultSetMetaData

              • getColumnCount(): 获取列数

              • getColumnLabel(): 获取列的别名

            2. 使用反射

            数据库的事务

            1. 数据库事务介绍

            • 事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。

            • 事务处理(事务操作):保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。

            • 为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。

            1. 数据库的ACID属性

            • 原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

            • 一致性(Consistency)事务必须使数据库从一个一致性状态变换到另外一个一致性状态。

            • 隔离性(Isolation)事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

            • 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。

            1. 数据库并发问题

            • 对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:

              • 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。

              • 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。

              • 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。

            • 数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。

            • 一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。


            1. 数据库四种隔离级别

            Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE。Oracle 默认的事务隔离级别为: READ COMMITED

            Mysql 支持 4 种事务隔离级别。Mysql 默认的事务隔离级别为: REPEATABLE READ。

            DAO及相关实现类

            DAO:Data Access Object访问数据信息的类和接口,包括了对数据的CRUD(Create、Retrival、Update、Delete),而不包含任何业务相关的信息。有时也称作:BaseDAO。作用:为了实现功能的模块化,更有利于代码的维护和升级。

            层级结构如下:

            BaseDao.java

              import java.lang.reflect.ParameterizedType;
              import java.lang.reflect.Type;
              import java.sql.Connection;
              import java.sql.SQLException;
              import java.util.List;


              import org.apache.commons.dbutils.QueryRunner;
              import org.apache.commons.dbutils.handlers.BeanHandler;
              import org.apache.commons.dbutils.handlers.BeanListHandler;
              import org.apache.commons.dbutils.handlers.ScalarHandler;




              /**
              * 定义一个用来被继承的对数据库进行基本操作的Dao
              *
              * @author HanYanBing
              *
              * @param <T>
              */
              public abstract class BaseDao<T> {
              private QueryRunner queryRunner = new QueryRunner();
              // 定义一个变量来接收泛型的类型
              private Class<T> type;


              // 获取T的Class对象,获取泛型的类型,泛型是在被子类继承时才确定
              public BaseDao() {
              // 获取子类的类型
              Class clazz = this.getClass();
              // 获取父类的类型
              // getGenericSuperclass()用来获取当前类的父类的类型
              // ParameterizedType表示的是带泛型的类型
              ParameterizedType parameterizedType = (ParameterizedType) clazz.getGenericSuperclass();
              // 获取具体的泛型类型 getActualTypeArguments获取具体的泛型的类型
              // 这个方法会返回一个Type的数组
              Type[] types = parameterizedType.getActualTypeArguments();
              // 获取具体的泛型的类型·
              this.type = (Class<T>) types[0];
              }


              /**
              * 通用的增删改操作
              *
              * @param sql
              * @param params
              * @return
              */
              public int update(Connection conn,String sql, Object... params) {
              int count = 0;
              try {
              count = queryRunner.update(conn, sql, params);
              } catch (SQLException e) {
              e.printStackTrace();
              }
              return count;
              }


              /**
              * 获取一个对象
              *
              * @param sql
              * @param params
              * @return
              */
              public T getBean(Connection conn,String sql, Object... params) {
              T t = null;
              try {
              t = queryRunner.query(conn, sql, new BeanHandler<T>(type), params);
              } catch (SQLException e) {
              e.printStackTrace();
              }
              return t;
              }


              /**
              * 获取所有对象
              *
              * @param sql
              * @param params
              * @return
              */
              public List<T> getBeanList(Connection conn,String sql, Object... params) {
              List<T> list = null;
              try {
              list = queryRunner.query(conn, sql, new BeanListHandler<T>(type), params);
              } catch (SQLException e) {
              e.printStackTrace();
              }
              return list;
              }


              /**
              * 获取一个但一值得方法,专门用来执行像 select count(*)...这样的sql语句
              *
              * @param sql
              * @param params
              * @return
              */
              public Object getValue(Connection conn,String sql, Object... params) {
              Object count = null;
              try {
              // 调用queryRunner的query方法获取一个单一的值
              count = queryRunner.query(conn, sql, new ScalarHandler<>(), params);
              } catch (SQLException e) {
              e.printStackTrace();
              }
              return count;
              }
              }

              BookDAO.java

                import java.sql.Connection;
                import java.util.List;


                import com.atguigu.bookstore.beans.Book;
                import com.atguigu.bookstore.beans.Page;


                public interface BookDao {


                /**
                * 从数据库中查询出所有的记录
                *
                * @return
                */
                List<Book> getBooks(Connection conn);


                /**
                * 向数据库中插入一条记录
                *
                * @param book
                */
                void saveBook(Connection conn,Book book);


                /**
                * 从数据库中根据图书的id删除一条记录
                *
                * @param bookId
                */
                void deleteBookById(Connection conn,String bookId);


                /**
                * 根据图书的id从数据库中查询出一条记录
                *
                * @param bookId
                * @return
                */
                Book getBookById(Connection conn,String bookId);


                /**
                * 根据图书的id从数据库中更新一条记录
                *
                * @param book
                */
                  void updateBook(Connection conn,Book book);
                }

                BookDaoImpl.java

                  import java.sql.Connection;
                  import java.util.List;


                  import com.atguigu.bookstore.beans.Book;
                  import com.atguigu.bookstore.beans.Page;
                  import com.atguigu.bookstore.dao.BaseDao;
                  import com.atguigu.bookstore.dao.BookDao;


                  public class BookDaoImpl extends BaseDao<Book> implements BookDao {


                  @Override
                  public List<Book> getBooks(Connection conn) {
                  // 调用BaseDao中得到一个List的方法
                  List<Book> beanList = null;
                  // 写sql语句
                  String sql = "select id,title,author,price,sales,stock,img_path imgPath from books";
                  beanList = getBeanList(conn,sql);
                  return beanList;
                  }


                  @Override
                  public void saveBook(Connection conn,Book book) {
                  // 写sql语句
                  String sql = "insert into books(title,author,price,sales,stock,img_path) values(?,?,?,?,?,?)";
                  // 调用BaseDao中通用的增删改的方法
                  update(conn,sql, book.getTitle(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(),book.getImgPath());
                  }


                  @Override
                  public void deleteBookById(Connection conn,String bookId) {
                  // 写sql语句
                  String sql = "DELETE FROM books WHERE id = ?";
                  // 调用BaseDao中通用增删改的方法
                  update(conn,sql, bookId);

                  }


                  @Override
                  public Book getBookById(Connection conn,String bookId) {
                  // 调用BaseDao中获取一个对象的方法
                  Book book = null;
                  // 写sql语句
                  String sql = "select id,title,author,price,sales,stock,img_path imgPath from books where id = ?";
                  book = getBean(conn,sql, bookId);
                  return book;
                  }


                  @Override
                  public void updateBook(Connection conn,Book book) {
                  // 写sql语句
                  String sql = "update books set title = ? , author = ? , price = ? , sales = ? , stock = ? where id = ?";
                  // 调用BaseDao中通用的增删改的方法
                  update(conn,sql, book.getTitle(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(), book.getId());
                    }
                  }


                  文章转载自Geeker工作坊,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                  评论