欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JDBC 编程

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

在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 设计结构

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

JDBC 编程

  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 //最终钱不会因为异常而变少