zoukankan      html  css  js  c++  java
  • JDBC 编程

    在Java语言中,有一个专门连接数据库的规范(JDBC),专门负责连接数据库进行数据操作。各个数据库提供商会根据这套规范(接口)编写相关的实现类,封装成一个 jar 包供用户下载使用。所以在进行编程时,需要将相应的 jar 包导入到工程文件下的 lib 目录下,并建立依赖。

    1 连接数据库并建表

    这里我们使用的是 mysql 数据库。

    1.1 加载注册驱动

    通过下述语句实现注册驱动,原理是这句语句会将 Driver.class 这份字节码加载到 JVM 中,然后 JVM 会执行该字节码的静态代码块,mysql 提供的这个驱动包中,Driver 的字节码内的静态代码块就完成了驱动对象的创建和注册。

    1 //加载注册驱动
    2 Class.forName("com.mysql.jdbc.Driver");

    1.2 连接数据库

    当我们注册了驱动之后,可以通过 DriverManager 获取与数据库的连接,需要传入三个参数:数据库的地址,登录用户名,登陆密码。注意 Connection 和 DriverManager 类都是 java.sql 包下的,dbName 是数据库的名字。

    1 //连接数据库
    2 Connection conn = DriverManager.getConnection(jdbc:mysql://localhost:3306/dbName", "root", "123");

    验证已经获取连接,可以在 mysql 控制台,使用命令:show processlist 查看运行进程。

    1.3 操作数据库

    当得到与数据的连接之后,我们还需要通过该连接获得一个语句对象,该语句对象内包含我们要对数据库执行的操作,也就是 SQL 语句。我们执行 SQL 语句一般使用语句对象的 executeUpdate 和 executeQuery 方法,前者用于 DDL 和 DML 操作,返回收影响的行数;后者用于执行 DQL 操作,返回结果集对象。具体使用方法如下

    1 //语句对象的获得
    2 Statement st = conn.createStatement();
    3 //执行语句,这里的语句用于建表
    4 st.executeUpdate("create table t_student (id int primary key auto_increment, name varchar(20), age int)")
    5 //释放资源,先进后出,需要处理异常
    6 st.close();
    7 conn.close();

    1.4 "贾琏欲执事"

    执行一次语句的步骤可以归为“贾(加)琏(连)欲(语)执(执)事(释)”这一句话,合在一起的源码为

     1 public class CreateTableTest {
     2     public static void main(String[] args) throws ClassNotFoundException, SQLException {
     3         Class.forName("com.mysql.jdbc.Driver");
     4         Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/jdbc_demo", "root", "1234");
     5         String sql = "create table t_student (id int primary key auto_increment, name varchar(20), age int)";
     6         
     7         Statement st = conn.createStatement();
     8         st.execute(sql);
     9         
    10         st.close();
    11         conn.close();
    12     }
    13 }

    2 DAO 层设计

    实际开发中,JavaWeb 开发代码一般分为三层,分层结构是 JavaWeb 开发中的一种设计思想,这样会让我们开发层次分明,每一层只要完成对应的功能即可,使得项目便于开发和维护。

    1. Web层/表现层 : 主要接受前台浏览器用户的参数,给浏览器响应数据等等
    2. Service层/业务成/服务层:主要处理业务功能,日志,权限,事物,等等
    3. DAO层/持久层:专门负责和数据库交互,数据处理相关代码
    DAO : Data Access Object 数据访问对象

    2.1 DAO 设计结构

    在实际开发中,可以按照下列结构进行设计

    1. 马赛克的地方是公司域名倒写加项目名
    2. 项目包下的 dao 包内放接口,规范了处理某个数据库需要的方法,名方式以 DAO 结尾
    3. dao 包内的 impl 包放具体的实现,命名方式以 DAOImpl 结尾
    4. 与 dao 包同级的 pojo 包内放用于接收数据库信息的简单普通对象
    5. test 包是用于测试实现类的各个方法
    6. util 包存放工具类
    7. dp.properties 存放了一些数据库连接需要用到的信息。

    2.2 代码的重构 - 代码复用

    在 1.4 中示范了怎么连接数据库进行一次建表操作,实际上 DAO 中的实现类内就是一个个这样的方法,比如常见的增、删、改、查,如果每个方法内都写这样一些列流程,会在后期维护中产生很大麻烦,假如连接密码改了,每个方法都要修改,这显然不现实,所以我们要利用工具类以及配置文档优化代码。

    • 连接数据库

    实现类中的方法应该专注于功能的实现,获得连接是该方法需要的一个结果,该方法并不关注这个过程,不应该放在方法内混淆语义。我们可以把连接数据库的代码放在 JdbcUtils 这个工具类内,该类的成员都是类成员,然后实现类的方法中直接通过类调用 getConnection() 方法获得连接。同时,注册驱动这个步骤我们只需要执行一次就够了,我们可以把这个步骤放在工具类的静态代码块中,在该类初始化的时候会自动执行一次。

     1 public class JdbcUtils {
     2     
     3     private static String driverClassName = "com.mysql.jdbc.Driver";
     4     private static String url = "jdbc:mysql://127.0.0.1:3306/jdbc_demo";
     5     private static String username = "root";
     6     private static String password = "1234";
     7     
     8     static {
     9         try {
    10             Class.forName(driverClassName);
    11         } catch (ClassNotFoundException e) {
    12             e.printStackTrace();
    13         }
    14     }
    15     
    16     public static Connection getConnection() throws SQLException {
    17         Connection conn = DriverManager.getConnection(url, username, password);
    18         return conn;
    19     }
    20 }
    • 关闭资源

    关闭资源也是一块鸡肋代码,重复且冗长,未重构前每个方法的关闭过程如下

     1 //假设是查询方法,除了关闭连接、语句还要关闭结果集,每次关闭都需要异常处理
     2 /*
     3 Connection conn;
     4 PreparedStatement ps;
     5 ResultSet rs;
     6 */
     7 try {
     8     //方法需要实现的功能
     9     return xxx;
    10 } catch (Exception e) {
    11     e.printStackTrace();
    12 } finally {
    13     //try 代码块中的 return 语句一定会在 finally 执行完后执行,所以关闭系统资源放在 finally 中。先进后出
    14     try {
    15         if(rs != null) {
    16             rs.close();
    17         }
    18     } catch (SQLException e) {
    19         e.printStackTrace();
    20     } finally {
    21         try {
    22             if(ps != null) {
    23                 ps.close();
    24             }
    25         } catch (SQLException e) {
    26             e.printStackTrace();
    27         } finally {
    28             try {
    29                 if(conn != null) {
    30                     conn.close();
    31                 }
    32             } catch (SQLException e) {
    33                 e.printStackTrace();
    34             }
    35         }
    36     }
    37 }

    可以看到这一段关闭资源的方法非常冗长,且每个方法都要关闭一遍。我们可以把这个关闭的流程写进工具类内,实现类的方法只需要调用 JdbcUtils.close() 方法即可。

     1 //Statement 是 PreparedStatement 的父类,这样不管是 Statement 或者 PreparedStatement 类型的都可以用这个方法关闭
     2 //对于 DML 语句,没有 ResultSet 对象,可以第三个变量穿 null 即可
     3 public static void close(Connection conn, Statement s, ResultSet rs) {
     4     try {
     5         if(rs != null) {
     6             rs.close();
     7         }
     8     } catch (Exception e) {
     9         e.printStackTrace();
    10     } finally {
    11         try {
    12             if(s != null) {
    13                 s.close();
    14             }
    15         } catch (Exception e) {
    16             e.printStackTrace();
    17         } finally {
    18             try {
    19                 if(conn != null) {
    20                     conn.close();
    21                 }
    22             } catch (Exception e) {
    23                 e.printStackTrace();
    24             }
    25         }
    26     }
    27 }
    • 建立连接需要的信息

    把连接需要的这些信息放在代码块中还是不太方便,如果用户修改了账号密码,或者主机地址改变,都需要重新修改代码,这不太满足我们平常应用的要求。所以我们可以把这些信息放在一个 .properties 文件中,Java 程序直接去读取这些信息。那以后修改了账户密码,只需要自己打开这个 .properties 文件修改相应的字段即可。

    #key=value
    driverClassName=com.mysql.jdbc.Driver
    url=jdbc:mysql://127.0.0.1:3306/jdbc_demo
    username=root
    password=1234
    .properties 内存放了一些键值对,注意等号左右没有空格,结尾也没有标点,# 作为注释

    那如何读取这些信息呢?

    Java 中通过类加载器读取配置文件获得一个输入流,可以通过两种方法获得类加载器

    1 //1.通过某一类的字节码实例可以获取
    2 ClassLoader cl = Object.class.getContextClassLoader();
    3 //2.通过当前线程获得
    4 ClassLoader cl = Thread.currentThread().getContextClassLoader();
    5 
    6 //通过类加载器读取配置文件获得输入流
    7 InputStream in = cl.getResourceAsStream("dp.properties");

    那如何获得相应的信息呢?

    我们可以通过 Properties 对象获得相应的信息。Properties 类是 Map 抽象类下一个专门用于读取配置文件的类,将输入流加载进去,即可通过“key”获得“value”。

    1 Properties p = new Properties();
    2 p.load(in);
    3 System.out.println(p.getProperty("driverClassName"));
    • 重构后的工具类代码
     1 public class JdbcUtils {
     2     
     3     private static Properties p = new Properties();
     4 
     5     static {
     6         ClassLoader cl = Thread.currentThread().getContextClassLoader();
     7         InputStream in = cl.getResourceAsStream("dp.properties");
     8         try {
     9             p.load(in);
    10             Class.forName(p.getProperty("driverClassName"));
    11         } catch (Exception e) {
    12             e.printStackTrace();
    13         }
    14     }
    15     
    16     public static Connection getConnection() throws SQLException {
    17         Connection conn = DriverManager.getConnection(p.getProperty("url"), p.getProperty("username"), p.getProperty("password"));
    18         return conn;
    19     }
    20     
    21     public static void close(Connection conn, Statement s, ResultSet rs) {
    22         try {
    23             if(rs != null) {
    24                 rs.close();
    25             }
    26         } catch (Exception e) {
    27             e.printStackTrace();
    28         } finally {
    29             try {
    30                 if(s != null) {
    31                     s.close();
    32                 }
    33             } catch (Exception e) {
    34                 e.printStackTrace();
    35             } finally {
    36                 try {
    37                     if(conn != null) {
    38                         conn.close();
    39                     }
    40                 } catch (Exception e) {
    41                     e.printStackTrace();
    42                 }
    43             }
    44         }
    45     }
    46 }

    2.3 引用连接池,管理连接

    按照上面的做法,每次用户进行一次操作,都会建立连接,接着执行完成后销毁连接。虽然每次连接、断开连接用时不长,但当用户数量上来意后,对系统资源的消耗就会变得很高。于是我们引入连接池管理连接。连接池里面拥有一定数量的连接(一般5 - 10个),当通过连接池getConnection 时,连接池提供一个连接供方法使用,当使用完毕后方法执行连接的 close 方法,这个时候并不是直接关闭连接,而是将连接返回给连接池。

    在Java中,连接池使用 javax.sql.DataSource 接口来表示连接池。注意:DataSource 仅仅只是一个接口,由各大服务器厂商来实现。常用的 DataSource 的实现:

    • DBCP: Spring推荐的
    • C3P0: Hibernate推荐的
    • Druid : (德鲁伊)阿里巴巴开源的,性能最好,速度最快

    介绍 Druid 使用方法

     1 //需要导入 Druid 的 jar 包
     2 //方法一:
     3 //1 创建连接池对象,不能使用 DataSource 类型,因为 setXxx 方法时 DruidDataSource 类独有的
     4 DruidDataSource dds = new DruidDataSource();
     5 //2 设置连接数据库的信息
     6 dds.setDriverClassName(p.getProperty("driverClassName"));
     7 dds.setUrl(p.getProperty("url"));
     8 dds.setUsername(p.getProperty("username"));
     9 dds.setPassword(p.getProperty("password"));
    10 dds.setMaxActive(10);  //最大连接数
    11 Connection conn = dds.getConnection();
    12 
    13 //方法二
    14 //通过连接池工程获得连接池
    15 //1 通过工厂的静态方法获得连接池,传入上文中的 Properties 对象作为参数,工程自动读取配置信息
    16 DataSource ds = DruidDataSourceFactory.createDataSource(p);
    17 //2 获得连接
    18 Connection conn = ds.getConnection();

    使用连接池后的工具类

     1 public class JdbcUtils {
     2     
     3     private static Properties p = new Properties();
     4 
     5     //用工厂创建连接池对象,工厂底层对 properties 直接读取,只需传入一个 Properties 对象
     6     private static DataSource dds;
     7     static {
     8         ClassLoader cl = Thread.currentThread().getContextClassLoader();
     9         InputStream in = cl.getResourceAsStream("dp.properties");
    10         try {
    11             p.load(in);
    12             dds = DruidDataSourceFactory.createDataSource(p);
    13         } catch (Exception e) {
    14             e.printStackTrace();
    15         }
    16     }
    17     
    18     public static Connection getConnection() throws SQLException {
    19         return dds.getConnection();
    20     }
    21     
    22     public static void close(Connection conn, Statement s, ResultSet rs) {
    23         try {
    24             if(rs != null) {
    25                 rs.close();
    26             }
    27         } catch (Exception e) {
    28             e.printStackTrace();
    29         } finally {
    30             try {
    31                 if(s != null) {
    32                     s.close();
    33                 }
    34             } catch (Exception e) {
    35                 e.printStackTrace();
    36             } finally {
    37                 try {
    38                     if(conn != null) {
    39                         conn.close();
    40                     }
    41                 } catch (Exception e) {
    42                     e.printStackTrace();
    43                 }
    44             }
    45         }
    46     }
    47 }

    3 事务

    JDBC 中是默认提交事务的,也就是每一条 Statement 执行后,自动提交事务。在实际业务中,我们可能需要把多个操作当作一个原子性操作来进行,要么全做,要么全部做。要实现这个要求,我们只需要把自动提交事务给关闭,在通过回滚(rollback)和提交(commit)来完成一次事务。

     1 //银行转账例子
     2 public class TransactionTest {
     3     @Test
     4     public void testName() throws Exception {
     5         Connection conn = null;
     6         Statement st = null;
     7         ResultSet rs = null;
     8         try {
     9             conn = DruidUtil.getConnection();
    10             //将事务设置为手动提交
    11             conn.setAutoCommit(false);
    12             
    13             st = conn.createStatement();
    14             // 1.检查张无忌的账号余额是否大于等于1000.
    15             rs = st.executeQuery("SELECT balance FROM account WHERE name = '张无忌' AND balance >=1000");
    16             if(!rs.next()) {
    17                 throw new RuntimeException("亲,您的账户余额不够");
    18             }
    19             // 余额>=1000:GOTO 2:
    20             // 余额 <1000:提示:亲,你的余额不足.
    21             // 2.在张无忌的账号余额上减少1000.
    22             st.executeUpdate("UPDATE account SET balance = balance-1000 WHERE name = '张无忌'");
    23             
    24             System.out.println(1/0);  //制造异常
    25             
    26             // 3.在赵敏的账户余额尚增加1000.
    27             st.executeUpdate("UPDATE account SET balance = balance+1000 WHERE name = '赵敏'");
    28             
    29             //提交事务
    30             conn.commit();
    31         } catch (Exception e) {
    32             e.printStackTrace();
    33             //回滚事务
    34             conn.rollback();    
    35         }finally {
    36             DruidUtil.close(conn, st, rs);
    37         }
    38     }
    39 }
    40 //最终钱不会因为异常而变少
  • 相关阅读:
    机器学习笔记(四)---- 逻辑回归的多分类
    在modelarts上部署backend为TensorFlow的keras模型
    深度学习在其他领域的应用1:密码破解
    Reactive(2) 响应式流与制奶厂业务
    如何把图片变得炫酷多彩,Python教你这样实现!
    漫谈边缘计算(三):5G的好拍档
    机器学习笔记(三)---- 逻辑回归(二分类)
    华为云数据库携新品惊艳亮相2019华为全联接大会
    100 个网络基础知识普及,看完成半个网络高手
    最大流
  • 原文地址:https://www.cnblogs.com/carlosouyang/p/10889724.html
Copyright © 2011-2022 走看看