本人的环境为Myeclipse10、MySQL5.7.15
本文包括:
简介
JDBC编程步骤
打通数据库
程序详解—DriverManager
程序详解—Connection
程序详解—Statement
程序详解—ResultSet
进阶应用—ResultSet滚动结果集
程序详解—释放资源
编写工具类简化CRUD操作
PreparedStatement-防止SQL注入
使用JDBC进行批处理
JavaEE体系结构
Paste_Image.png
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, "[email protected]");
// 确认动作
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','[email protected]','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的增删改查