本人的环境为Myeclipse10、MySQL5.7.15
本文包括:
- 简介
- JDBC编程步骤
- 打通数据库
- 程序详解—DriverManager
- 程序详解—Connection
- 程序详解—Statement
- 程序详解—ResultSet
- 进阶应用—ResultSet滚动结果集
- 程序详解—释放资源
- 编写工具类简化CRUD操作
- PreparedStatement-防止SQL注入
- 使用JDBC进行批处理
- JavaEE体系结构
1、 简介
Java Data Base Connectivity(Java数据库连接):是java与数据库的桥梁,提供读写操作。
可以为多种数据库提供统一的访问,是一种统一标准。
通过JDBC可以连接Oracle、MySql、Sql Server数据库。
2、JDBC编程步骤
简单来说:
加载驱动程序
建立连接
操作数据
释放资源
具体而言:
通过DriverManager加载驱动程序driver;
通过DriverManager类获得表示数据库连接的Connection类对象;
通过Connection对象绑定要执行的语句,生成Statement类对象;
执行SQL语句,接收执行结果集ResultSet;
可选的对结果集ResultSet类对象的处理;
必要的关闭ResultSet、Statement和Connection
3、打通数据库
-
需要导入mysql-connector-java的jar包,如图所示:
注意:可以直接将数据库驱动的jar包复制到/WebRoot/WEB/INF/lib/目录下,这时候根目录的Referenced Libraries 中会直接出现这个jar包。如果像图示的这样操作,那么还需要在这个jar包上单击右键-build path,把它添加到path中。
-
加载驱动程序:
Class.forName(driverClass)
-
加载Mysql驱动:
Class.forName("com.mysql.jdbc.Driver")
-
加载Oracle驱动:
Class.forName("oracle.jdbc.driver.OracleDriver")
为什么要用反射技术来加载驱动程序呢?详情见下文DriverManager的介绍。
-
-
获得数据库连接:
-
getConnection方法:
DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/imooc", USER, PASSWORD);
其中jdbc:mysql表示jdbc连接mysql,127.0.0.1:3306为服务器地址和端口,imooc为数据库名称,USER和PASSWORD分别为数据库的用户名和密码。
-
URL:
jdbc:mysql://localhost:3306/test?key=value&key=value
-
省略写法:
jdbc:mysql:///test
-
URL中常用的参数:
useUnicode=true&characterEncoding=UTF-8(注意:这里的字符集应该与客户端保持一致)
-
-
-
通过数据库的连接操作数据库,创建Statement对象:
Statement stmt = conn.createStatement();
4、程序详解—DriverManager
-
Jdbc程序中的DriverManager用于加载驱动,并创建与数据库的链接,这个API的常用方法:
DriverManager.registerDriver(new Driver()); DriverManager.getConnection(url, user, password);
注意:在实际开发中并不推荐采用registerDriver方法注册驱动。
原因有二:
查看Driver的源代码可以看到,如果采用此种方式,会导致驱动程序注册两次,也就是在内存中会有两个Driver对象。
程序依赖mysql的api,脱离mysql的jar包,程序将无法编译,将来程序切换底层数据库将会非常麻烦。
-
推荐方式:
Class.forName(“com.mysql.jdbc.Driver”);
采用此种方式不会导致驱动对象在内存中重复出现,并且采用此种方式,程序仅仅只需要一个字符串,不需要依赖具体的驱动,即不需要import相关的包,使程序的灵活性更高。
同样,在开发中也不建议采用具体的驱动类型指向getConnection方法返回的connection对象。
5、程序详解—Connection
-
Jdbc程序中的Connection,它用于代表数据库的链接,Collection是数据库编程中最重要的一个对象,客户端与数据库所有交互都是通过connection对象完成的,这个对象的常用方法有两种:
-
获得操作数据库Statement对象
- createStatement():创建向数据库发送sql的statement对象
prepareStatement(String sql) :创建向数据库发送预编译sql的PrepareSatement对象,它是statement的子接口。
prepareCall(sql):创建执行存储过程的callableStatement对象,它是PrepareStatement的子接口。 --- 存储过程
-
进行事务控制
- setAutoCommit(boolean autoCommit):设置事务是否自动提交。
commit() :在链接上提交事务。 ---与事务相关!!
rollback() :在此链接上回滚事务。
-
-
示例:
Statement stmt = conn.createStatement();
6、程序详解—Statement
-
Jdbc程序中的Statement对象用于向数据库发送SQL语句, Statement对象常用方法:
executeQuery(String sql) :用于向数据发送查询语句。select语句,返回值ResultSet结果集。
executeUpdate(String sql):用于向数据库发送insert、update或delete语句。返回值为int:受影响行数。
-
execute(String sql):用于向数据库发送任意sql语句,返回值为boolean:如果第一个结果为 ResultSet 对象,则返回 true;如果其为更新计数或者不存在任何结果,则返回 false 。
批处理:
addBatch(String sql) :把多条sql语句放到一个批处理中。
executeBatch():向数据库发送一批sql语句执行。
-
示例:
//executeQuery(String sql) 用法 ResultSet rs = stmt.executeQuery("select * from users"); //execute(String sql)用法 stmt.execute("update a set name ='bbb' where id = 1"); //executeUpdate(String sql)用法 int row = stmt.executeUpdate(sql);
7、程序详解—ResultSet
Jdbc程序中的ResultSet用于代表Sql语句的执行结果。Resultset封装执行结果时,采用的类似于表格的方式。ResultSet 对象维护了一个指向表格数据行的游标cursor,初始的时候,游标在第一行之前,调用ResultSet.next() 方法,可以使游标指向具体的数据行,进而调用方法获取该行的数据。
-
ResultSet既然用于封装执行结果的,所以该对象提供的大部分方法都是用于获取数据的get方法:
-
获取任意类型的数据
getObject(int index)
getObject(string columnName)
-
获取指定类型的数据,例如:
getString(int index)
getString(String columnName)
getInt(int index)
getInt(String columnNmae)
...
-
常用数据类型转换表
-
遍历查询结果
-
-
示例:
ResultSet rs = stmt.executeQuery("select * from users"); while (rs.next()) { System.out.println(rs.getInt("id")); System.out.println(rs.getString("name")); System.out.println(rs.getString("pwd")); System.out.println(rs.getString("email")); // 通过rs进行取值时,可以使用列名 或 索引 ---- 经常用列名,因为可读性更强 System.out.println(rs.getInt(1)); System.out.println(rs.getString(2)); System.out.println(rs.getString(3)); System.out.println(rs.getString(4)); System.out.println("-----------------------------"); }
-
思考:如果明知道某条SQL语句只返回一行数据,还用while?
if(rs.next){ // 因为结果只有1行,存在,不存在 }
8、进阶应用—ResultSet滚动结果集
-
ResultSet还提供了对结果集进行滚动和更新的方法。
若想设置可滚动的结果集,则在创建Statement对象时,不能像前文那样调用无参方法,而应该如下设置:Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);
next():移动到下一行
previous():移动到前一行
absolute(int row):移动到指定行
beforeFirst():移动resultSet的最前面
afterLast() :移动到resultSet的最后面
updateString(int columnIndex, String x) :用 String 值更新指定列。
updateString(String columnLabel, String x) :用 String 值更新指定列。
...
updateRow() :更新行数据,最后要调用这个方法来确认
-
示例:
public void demo5() throws Exception { // 设置可滚动结果集 Class.forName("com.mysql.jdbc.Driver"); Connection conn = DriverManager.getConnection("jdbc:mysql:///day13", "root", "123"); // 参数:TYPE_SCROLL_SENSITIVE 可滚动 、CONCUR_UPDATABLE 可修改 Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet rs = stmt.executeQuery("select * from users"); // 游标移动到rs的第三行 rs.absolute(3); System.out.println(rs.getString("name")); // 更新第四列的值 rs.updateString(4, "def@itcast.cn"); // 确认动作 rs.updateRow(); rs.close(); stmt.close(); conn.close(); }
-
可以用以下两种方式使用更新方法:
-
更新当前行中的列值。在可滚动的 ResultSet 对象中,可以向前和向后移动光标,将其置于绝对位置或相对于当前行的位置。以下代码片段更新 ResultSet 对象 rs 第五行中的 NAME 列,然后使用方法 updateRow 更新导出 rs 的数据源表。
rs.absolute(5); // moves the cursor to the fifth row of rs rs.updateString("NAME", "AINSWORTH"); // updates the // NAME column of row 5 to be AINSWORTH rs.updateRow(); // updates the row in the data source
-
将列值插入到插入行中。可更新的 ResultSet 对象具有一个与其关联的特殊行,该行用作构建要插入的行的暂存区域 (staging area)。以下代码片段将光标移动到插入行,构建一个三列的行,并使用方法 insertRow 将其插入到 rs 和数据源表中。
rs.moveToInsertRow(); // moves cursor to the insert row rs.updateString(1, "AINSWORTH"); // updates the // first column of the insert row to be AINSWORTH rs.updateInt(2,35); // updates the second column to be 35 rs.updateBoolean(3, true); // updates the third column to true rs.insertRow(); rs.moveToCurrentRow();
-
9、程序详解—释放资源
Jdbc程序运行完后,切记要释放程序在运行过程中,创建的那些与数据库进行交互的对象,这些对象通常是ResultSet, Statement和Connection对象。
特别是Connection对象,它是非常稀有的资源,用完后必须马上释放,如果Connection不能及时、正确的关闭,极易导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。
为确保资源释放代码能运行,资源释放代码也一定要放在finally语句中。
-
释放资源的标准写法:
public void demo6() throws ClassNotFoundException { // 释放资源 标准写法 Class.forName("com.mysql.jdbc.Driver"); Connection conn = null; Statement stmt = null; ResultSet rs = null; try { conn = DriverManager.getConnection("jdbc:mysql:///day13", "root", "123"); stmt = conn.createStatement(); rs = stmt.executeQuery("select * from users"); while (rs.next()) { System.out.println(rs.getString("name")); } } catch (SQLException e) { e.printStackTrace(); } finally { //标准写法:先关rs、再stmt、最后conn if (rs != null) { try { rs.close(); } catch (SQLException sqlEx) { } rs = null; } if (stmt != null) { try { stmt.close(); } catch (SQLException sqlEx) { } stmt = null; } if (conn != null) { try { conn.close(); } catch (SQLException e) { } conn = null; } } }
10、编写工具类简化CRUD操作
dbconfig.properties文件:
DRIVERCLASS=com.mysql.jdbc.Driver
URL=jdbc:mysql:///day14
USER=root
PWD=123
#DRIVERCLASS=oracle.jdbc.driver.OracleDriver
#URL=jdbc:oracle:thin:@localhost:1521:xe
#USER=system
#PWD=123
JDBCUtils文件:
package cn.itcast.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ResourceBundle;
/**
* JDBC 工具类,抽取公共方法
*
* @author seawind
*
*/
public class JDBCUtils {
private static final String DRIVERCLASS;
private static final String URL;
private static final String USER;
private static final String PWD;
//从dbconfig.properties文件中得到四个参数,方便切换数据库
static {
ResourceBundle bundle = ResourceBundle.getBundle("dbconfig");
DRIVERCLASS = bundle.getString("DRIVERCLASS");
URL = bundle.getString("URL");
USER = bundle.getString("USER");
PWD = bundle.getString("PWD");
}
// 建立连接
public static Connection getConnection() throws Exception {
loadDriver();
return DriverManager.getConnection(URL, USER, PWD);
}
// 装载驱动
private static void loadDriver() throws ClassNotFoundException {
Class.forName(DRIVERCLASS);
}
// 释放资源
public static void release(ResultSet rs, Statement stmt, Connection conn) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
release(stmt, conn);
}
public static void release(Statement stmt, Connection conn) {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
}
11、PreparedStatement-防止SQL注入
-
SQL注入是用户利用某些系统没有对输入数据进行充分的检查,从而进行恶意破坏的行为。
-
PreparedStatement是Statement的子接口,它的实例对象可以通过调用Connection.preparedStatement()方法获得,相对于Statement对象而言:
PreperedStatement可以避免SQL注入的问题。
Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出。PreparedStatement 可对SQL进行预编译,从而提高数据库的执行效率。
PreperedStatement对于sql中的参数,允许使用占位符的形式进行替换,简化sql语句的编写。
-
示例:
public User login(User user) { // JDBC查询 User existUser = null; Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; try { conn = JDBCUtils.getConnection(); String sql = "select * from users where name = ? and pwd = ?"; // 数据库编译时 stmt = conn.prepareStatement(sql); // 将sql 发送给数据库进行编译 // 设置参数 stmt.setString(1, user.getName()); // or -- 传入数据值,不会作为关键字 --防止注入 stmt.setString(2, user.getPwd()); // 因为之前 将sql 传递数据库 rs = stmt.executeQuery(); // 如果登陆成功 只有一条记录 if (rs.next()) { existUser = new User(); existUser.setId(rs.getInt("id")); existUser.setName(rs.getString("name")); existUser.setPwd(rs.getString("pwd")); existUser.setEmail(rs.getString("email")); } } catch (Exception e) { e.printStackTrace(); } return existUser; }
12、使用JDBC进行批处理
业务场景:当需要向数据库发送一批SQL语句执行时,应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,以提升执行效率。
实现批处理有两种方式
-
第一种方式,Statement.addBatch(sql):
executeBatch()方法:执行批处理命令,将这组sql一次性发送数据库
clearBatch()方法:清除批处理命令
-
示例:
Connection conn = null; Statement st = null; ResultSet rs = null; try { conn = JdbcUtil.getConnection(); String sql1 = "insert into person(name,password,email,birthday) values('kkk','123','abc@sina.com','1978-08-08')"; String sql2 = "update user set password='123456' where id=3"; st = conn.createStatement(); st.addBatch(sql1); //把SQL语句加入到批命令中 st.addBatch(sql2); //把SQL语句加入到批命令中 st.executeBatch(); } finally{ JdbcUtil.free(conn, st, rs); }
-
采用Statement.addBatch(sql)方式实现批处理:
优点:可以向数据库发送多条不同的SQL语句。
-
缺点:
SQL语句没有预编译。
-
当向数据库发送多条语句相同,但仅参数不同的SQL语句时,需重复写上很多条SQL语句,会导致数据库编译sql语句四次 ---- 性能比较差
。例如:Insert into user(name,password) values(‘aa’,’111’); Insert into user(name,password) values(‘bb’,’222’); Insert into user(name,password) values(‘cc’,’333’); Insert into user(name,password) values(‘dd’,’444’);
-
第二种方式,PreparedStatement.addBatch():
如果连续执行多条结构相同sql --- 采用预编译 ---- SQL只需要编译一次
-
向数据库插入50000条数据示例:
conn = JdbcUtil.getConnection(); String sql = "insert into person(name,password,email,birthday) values(?,?,?,?)"; st = conn.prepareStatement(sql); for(int i=0;i<50000;i++){ st.setString(1, "aaa" + i); st.setString(2, "123" + i); st.setString(3, "aaa" + i + "@sina.com"); st.setDate(4,new Date(1980, 10, 10)); st.addBatch(); if(i%1000==0){ st.executeBatch(); st.clearBatch(); } } st.executeBatch();
-
采用PreparedStatement.addBatch()实现批处理
优点:发送的是预编译后的SQL语句,执行效率高。
缺点:只能应用在SQL语句相同,但参数不同的批处理中。因此此种形式的批处理经常用于在同一个表中批量插入数据,或批量更新表的数据。
13、JavaEE体系结构
-
MVC 和 JavaEE经典三层结构 由两拨人分别提出的
三层结构中业务层、数据持久层 ---Model
三层结构中web层 Servlet ---Controller
三层结构中web层 JSP ---View
-
JavaEE模式-DAO(Data Access Object)模式:
封装对于数据源的操作
数据源可能是文件(如xml)、数据库等任意存储方式
负责管理与数据源的连接
负责数据的存取(CRUD)
-
DAO 模式中的对象
Business Object:代表数据的使用者(业务层程序)
DataAccessObject:抽象并封装了对底层数据源的操作(数据层程序)
DataSource:数据源(mysql数据库)
TransferObject:表示数据的Java Bean
BussinessObject 通过 将transferObject 传递 DataAccessObject 完成对DataSource的增删改查
JDBC文集:
Java 与数据库的桥梁——JDBC:http://www.jianshu.com/p/c0acbd18794c
JDBC 进阶——连接池:http://www.jianshu.com/p/ad0ff2961597
JDBC 进阶——元数据:http://www.jianshu.com/p/36d5d76342f1
JDBC框架——DBUtils:http://www.jianshu.com/p/10241754cdd7