熟悉了JDBC的编程步骤后,接下来通过一个案例并依照上一小节所讲解的步骤来演示JDBC的使用。此案例会从tb_user表中读取数据,并将结果打印在控制台。
需要说明的是,Java中的JDBC是用来连接数据库从而执行相关数据相关操作的,因此在使用JDBC时,一定要确保安装有数据库。常用的关系型数据库有MySQL和Oracle,本书就以连接MySQL数据库为例,使用JDBC执行相关操作。
案例的具体实现步骤如下:
(1) 搭建数据库环境
在MySQL数据库中创建一个名称为jdbc的数据库,然后在该数据库中创建一个名称为tb_user的表,创建数据库和表的SQL语句如下:
CREATE DATABASE jdbc;USE jdbc;
CREATE TABLEtb_user(
id INT PRIMARY KEY AUTO_INCREMENT,NAMEVARCHAR(40),
sex VARCHAR(2),
email VARCHAR(60),
birthday DATE
);
上述创建tb_user表时添加了id、NAME、sex、email和birthday共5个字段,其中NAME字段名称为大写形式,这是因为name字段在MySQL数据库中属于关键字,所以为了方便区分,这里将name字段名称全部用大写表示。
数据库和表创建成功后,再向tb_user表中插入3条数据,插入的SQL语句如下所示:
INSERT INTOtb_user(NAME,sex,email,birthday)VALUES('Jack','男','jack@126.com','1980-01-04'),('Tom','男','tom@126.com','1981-02-14'),('Lucy','女','lucy@126.com','1979-12-28');
为了查看数据是否添加成功,使用SELECT语句查询tb_user表中的数据,执行结果如图1所示。
图1 tb_user表中的数据
注意:
数据库和表创建成功后,如果使用的是命令行窗口向tb_user表中插入带有中文的数据,命令行窗口可能会报错,同时从MySQL数据库查询带有中文数据还可能会显示乱码,这是因为MySQL数据库默认使用的是UTF-8编码格式,而命令行窗口默认使用的是GBK编码格式,所以执行带有中文数据的插入语句会出现解析错误。为了在命令行窗口也能正常向MySQL数据库插入中文数据,以及查询中文数据,可以在执行插入语句和查询语句前,先在命令行窗口执行以下两条命令:
set character_set_client=gbk;
set character_set_results=gbk;
执行完上述两条命令后,再次在命令行窗口执行插入和查询操作就不再出现乱码问题了。
(1) 创建项目环境,导入数据库驱动
在Eclipse中新建一个名称为chapter09的Java项目,使用鼠标右键单击项目名称,然后选择【New】→【Folder】,在弹出窗口中将该文件夹命名为lib并单击【Finish】按钮,此时项目根目录中就会出现一个名称为lib的文件夹。将下载好的MySQL数据库驱动文件Jar包(mysql-connector-java-5.1.46-bin.jar)复制到项目的lib目录中,并使用鼠标右击该JAR包,在弹出框中选择【Build Path】→【Add to Build Path】,此时Eclipse会将该JAR包发布到类路径下。加入驱动后的项目结构如图2所示。
其中,MySQL驱动文件可以在其官网地址:http://dev.mysql.com/downloads/connector/j/页面中下载,单击页面Platform Independent (Architecture Independent), ZIP Archive后的“Download”按钮,并在新打开的窗口中的单击 “No thanks, just start my download”超链接后即可下载驱动压缩包(本书编写时的最新驱动版本文件是mysql-connector-java-5.1.46.zip),解压后即可得到相应JAR包。
(1) 编写JDBC程序
在项目chapter09的src目录下,新建一个名称为com.itheima.jdbc的包,并在该包中创建类Example01。在该类读取数据库中的tb_user表,并将结果输出到控制台,如文件1所示。
文件1 Example01.java
importjava.sql.*;publicclassExample01{publicstaticvoidmain(String[] args)throwsSQLException{Connection conn =null;Statement stmt =null;ResultSet rs =null;try{// 1. 加载数据库驱动Class.forName("com.mysql.jdbc.Driver");// 2.通过DriverManager获取数据库连接String url ="jdbc:mysql://localhost:3306/jdbc";String username ="root";String password ="root";
conn =DriverManager.getConnection(url,username, password);// 3.通过Connection对象获取Statement对象
stmt = conn.createStatement();// 4.使用Statement执行SQL语句String sql ="select * from tb_user";
rs = stmt.executeQuery(sql);// 5. 操作ResultSet结果集System.out.println("id | name | sex "+" | email | birthday ");while(rs.next()){int id = rs.getInt("id");// 通过列名获取指定字段的值String name = rs.getString("name");String sex = rs.getString("sex");String email = rs.getString("email");Date birthday = rs.getDate("birthday");System.out.println(id +" | "+ name +" | "+ sex +" | "+ email +" | "+ birthday);}}catch(Exception e){
e.printStackTrace();}finally{// 6.关闭连接,释放资源if(rs !=null){ rs.close();}if(stmt !=null){ stmt.close();}if(conn !=null){ conn.close();}}}}
运行结果如图3所示。
图3 运行结果
文件1中,首先注册了MySQL数据库驱动,通过DriverManager获取一个Connection对象,然后使用Connection对象创建一个Statement对象,Statement对象通过executeQuery(String sql)方法执行了SQL语句,并返回结果集ResultSet,接下来,通过遍历ResultSet得到查询结果并输出,最后关闭连接,释放数据库资源。
从图3中可以看到,tb_user表中的数据已被打印在了控制台。至此第一个JDBC程序实现成功。
注意:
在进行数据库连接时,连接MySQL数据库的username和password都要与创建MySQL数据库时设置的登录账户一致,否则登录失败。本章以及后续案例都默认MySQL数据库登录的username和password都为“root”。
实现JDBC程序一共需要几步?
通常情况下,JDBC的使用可以按照以下几个步骤进行:
(1) 加载数据库驱动
加载数据库驱动通常使用Class类的静态方法forName()来实现,具体实现方式如下:
Class.forName("DriverName");
在上述代码中,DriverName就是数据库驱动类所对应的字符串。例如,要加载MySQL数据库的驱动可以采用如下代码:
Class.forName("com.mysql.jdbc.Driver");
加载Oracle数据库的驱动可以采用如下代码:
Class.forName("oracle.jdbc.driver.OracleDriver");
从上面两种加载数据库驱动的代码可以看出,在加载驱动时所加载的并不是真正使用数据库的驱动类,而是数据库驱动类名的字符串。
(2) 通过DriverManager获取数据库连接
DriverManager中提供了一个getConnection()方法来获取数据库连接,获取方式如下:
Connection conn =DriverManager.getConnection(String url,String user,String pwd);
从上述代码可以看出,getConnection()方法中有3个参数,它们分别表示连接数据库的URL、登录数据库的用户名和密码。其中用户名和密码通常由数据库管理员设置,而连接数据库的URL则遵循一定的写法。以MySQL数据库为例,其地址的书写格式如下:
jdbc:mysql://hostname:port/databasename
上面代码中,jdbc:mysql:是固定的写法,mysql指的是MySQL数据库。hostname指的是主机的名称(如果数据库在本机上,hostname可以为localhost或127.0.0.1,如果在其他机器上,那么hostname为所要连接机器的IP地址),port指的是连接数据库的端口号(MySQL端口号默认为3306),databasename指的是MySQL中相应数据库的名称。
(3) 通过Connection对象获取Statement对象
Connection创建Statement的方式有如下三种:
● createStatement():创建基本的Statement对象。
● prepareStatement(String sql):根据传递的SQL语句创建PreparedStatement对象。
● prepareCall(String sql):根据传入的SQL语句创建CallableStatement对象。
以创建基本的Statement对象为例,其创建方式如下:
Statement stmt = conn.createStatement();
(4) 使用Statement执行SQL语句
所有的Statement都有如下三种执行SQL语句的方法:
● execute(String sql):用于执行任意的SQL语句。
● executeQuery(String sql):用于执行查询语句,返回一个ResultSet结果集对象。
● executeUpdate(String sql):主要用于执行DML(数据操作语言)和DDL(数据定义语言)语句。执行DML语句(INSERT、UPDATE或DELETE)时,会返回受SQL语句影响的行数,执行DDL(CREATE、ALTER)语句返回0。
以executeQuery()方法为例,其使用方式如下:
// 执行SQL语句,获取结果集ResultSetResultSet rs = stmt.executeQuery(sql);
(5) 操作ResultSet结果集
如果执行的SQL语句是查询语句,执行结果将返回一个ResultSet对象,该对象里保存了SQL语句查询的结果。程序可以通过操作该ResultSet对象来取出查询结果。
(6) 关闭连接,释放资源
每次操作数据库结束后都要关闭数据库连接,释放资源,以重复利用资源。需要注意的是,通常资源的关闭顺序与打开顺序相反,顺序是ResultSet、Statement(或PreparedStatement)和Connection。为了保证在异常情况下也能关闭资源,需要在try...catch的finally代码块中统一关闭资源。
至此,JDBC程序的大致实现步骤已经讲解完成。
Spring中的JDK动态代理是如何实现的?
JDK动态代理是通过java.lang.reflect.Proxy 类来实现的,我们可以调用Proxy类的newProxyInstance()方法来创建代理对象。对于使用业务接口的类,Spring默认会使用JDK动态代理来实现AOP。
接下来,通过一个案例来演示Spring中JDK动态代理的实现过程,具体步骤如下。
(1)创建一个名为chapter03的Web项目,导入Spring框架所需JAR包到项目的lib目录中,并发布到类路径下。
(2)在src目录下,创建一个com.itheima.jdk包,在该包下创建接口UserDao,并在该接口中编写添加和删除的方法,如文件1所示。
文件1 UserDao.java
packagecom.itheima.jdk;publicinterfaceUserDao{publicvoidaddUser();publicvoiddeleteUser();}
(3)在com.itheima.jdk包中,创建UserDao接口的实现类UserDaoImpl,分别实现接口中的方法,并在每个方法中添加一条输出语句,如文件2所示。
文件2 UserDaoImpl.java
packagecom.itheima.jdk;// 目标类publicclassUserDaoImplimplementsUserDao{publicvoidaddUser(){System.out.println("添加用户");}publicvoiddeleteUser(){System.out.println("删除用户");}}
需要注意的是,本案例中会将实现类UserDaoImpl作为目标类,对其中的方法进行增强处理。
(4)在src目录下,创建一个com.itheima.aspect包,并在该包下创建切面类MyAspect,在该类中定义一个模拟权限检查的方法和一个模拟记录日志的方法,这两个方法就表示切面中的通知,如文件3所示。
文件3 MyAspect.java
packagecom.itheima.aspect;//切面类:可以存在多个通知Advice(即增强的方法)publicclassMyAspect{publicvoidcheck_Permissions(){System.out.println("模拟检查权限...");}publicvoidlog(){System.out.println("模拟记录日志...");}}
(5)在com.itheima.jdk包下,创建代理类JdkProxy,该类需要实现InvocationHandler接口,并编写代理方法。在代理方法中,需要通过Proxy类实现动态代理,如文件4所示。
文件4 JdkProxy.java
packagecom.itheima.jdk;importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Method;importjava.lang.reflect.Proxy;importcom.itheima.aspect.MyAspect;/**
* JDK代理类
*/publicclassJdkProxyimplementsInvocationHandler{// 声明目标类接口privateUserDao userDao;// 创建代理方法publicObjectcreateProxy(UserDao userDao){this.userDao = userDao;// 1.类加载器ClassLoader classLoader =JdkProxy.class.getClassLoader();// 2.被代理对象实现的所有接口Class[] clazz = userDao.getClass().getInterfaces();// 3.使用代理类,进行增强,返回的是代理后的对象returnProxy.newProxyInstance(classLoader,clazz,this);}/*
* 所有动态代理类的方法调用,都会交由invoke()方法去处理
* proxy 被代理后的对象
* method 将要被执行的方法信息(反射)
* args 执行方法时需要的参数
*/@OverridepublicObjectinvoke(Object proxy,Method method,Object[] args)throwsThrowable{// 声明切面MyAspect myAspect =newMyAspect();// 前增强
myAspect.check_Permissions();// 在目标类上调用方法,并传入参数Object obj = method.invoke(userDao, args);// 后增强
myAspect.log();return obj;}}
在文件4中,JdkProxy类实现了InvocationHandler接口,并实现了接口中的invoke()方法,所有动态代理类所调用的方法都会交由该方法处理。在创建的代理方法createProxy()中,使用了Proxy类的newProxyInstance()方法来创建代理对象。newProxyInstance()方法中包含三个参数,其中第1个参数是当前类的类加载器,第2个参数表示的是被代理对象实现的所有接口,第3个参数this代表的就是代理类JdkProxy本身。在invoke()方法中,目标类方法执行的前后,会分别执行切面类中的check_Permissions()方法和log()方法。
(6)在com.itheima.jdk包中,创建测试类JdkTest。在该类中的main()方法中创建代理对象和目标对象,然后从代理对象中获得对目标对象userDao增强后的对象,最后调用该对象中的添加和删除方法,如文件5所示。
文件5 JdkTest.java
packagecom.itheima.jdk;publicclassJdkTest{publicstaticvoidmain(String[] args){// 创建代理对象JdkProxy jdkProxy =newJdkProxy();// 创建目标对象UserDao userDao=newUserDaoImpl();// 从代理对象中获取增强后的目标对象UserDao userDao1 =(UserDao) jdkProxy.createProxy(userDao);// 执行方法
userDao1.addUser();
userDao1.deleteUser();}}
执行程序后,控制台的输出结果如图1所示。
图1 运行结果
从图1可以看出,userDao实例中的添加用户和删除用户的方法已被成功调用,并且在调用前后分别增加了检查权限和记录日志的功能。这种实现了接口的代理方式,就是Spring中的JDK动态代理。