一、 JDBC 简介
1 什么是 JDBC
• JDBC(Java DataBase Connectivity)java 数据库连接
• 是 JavaEE 平台下的技术规范
• 定义了在 Java 语言中连接数据,执行 SQL 语句的标准
• 可以为多种关系数据库提供统一访问
2 什么是数据库驱动程序
• 数据库厂商对 JDBC 规范的具体实现
• 不同数据产品的数据库驱动名字有差异
• 在程序中需要依赖数据库驱动来完成对数据库的操作
二、 JDBC3.0 标准中常用接口与类
1 Driver 接口
Driver 接口的作用是来定义数据库驱动对象应该具备的一些能力。比如与数据库建立连
接的方法的定义所有支持 java 语言连接的数据库都实现了该接口,实现该接口的类我们称
之为数据库驱动类。在程序中要连接数据库,必须先通过 JDK 的反射机制加载数据库驱动
类,将其实例化。不同的数据库驱动类的类名有区别。
加载 MySql 驱动:Class.forName("com.mysql.jdbc.Driver");
加载 Oracle 驱动:Class.forName("oracle.jdbc.driver.OracleDriver");
2 DriverManager 类
DriverManager 通过实例化的数据库驱动对象,能够建立应用程序与数据库之间建立连
接。并返回 Connection 接口类型的数据库连接对象。
2.1常用方法
• getConnection(String jdbcUrl, String user, String password)
该方法通过访问数据库的 url、用户以及密码,返回对应的数据库的 Connection 对象。
2.2 JDBC URL
与数据库连接时,用来连接到指定数据库标识符。在 URL 中包括了该数据库的类型、地址、端口、库名称等信息。不同品牌数据库的连接 URL 不同。
3 Connection 接口
Connection 与数据库的连接(会话)对象。我们可以通过该对象执行 sql 语句并返回结
果。
连接 MySql 数据库:
Connection
连接 Oracle 数据库:
Connection conn=DriverManager.getConnection("jdbc:oracle:thin:@host:port:database", "user","password");
连接 SqlServer 数据库:
Connection conn = DriverManager.getConnection("jdbc:microsoft:sqlserver://host:port;DatabaseName=database", "user", "password");
3.1常用方法
• createStatement():创建向数据库发送 sql 的 Statement 接口类型的对象。
• preparedStatement(sql) :创建向数据库发送预编译 sql 的 PrepareSatement 接口类型的对象。
• prepareCall(sql):创建执行存储过程的 CallableStatement 接口类型的对象。
• setAutoCommit(boolean autoCommit):设置事务是否自动提交。
• commit() :在链接上提交事务。
• rollback() :在此链接上回滚事务。
4 Statement 接口
用于执行静态 SQL 语句并返回它所生成结果的对象。
由 createStatement 创建,用于发送简单的 SQL 语句(不支持动态绑定)。
4.1常用方法
• execute(String sql):执行参数中的 SQL,返回是否有结果集。
• executeQuery(String sql):运行 select 语句,返回 ResultSet 结果集。
• executeUpdate(String sql):运行 insert/update/delete 操作,返回更新的行数。
• addBatch(String sql) :把多条 sql 语句放到一个批处理中。
• executeBatch():向数据库发送一批 sql 语句执行。
5 PreparedStatement 接口
继承自 Statement 接口,由 preparedStatement 创建,用于发送含有一个或多个参数的 SQL语句。PreparedStatement 对象比 Statement 对象的效率更高,并且可以防止 SQL 注入,所以我们一般都使用 PreparedStatement。
5.1常用方法
• addBatch()把当前 sql 语句加入到一个批处理中。
• execute() 执行当前 SQL,返回个 boolean 值
• executeUpdate()运行 insert/update/delete 操作,返回更新的行数。
• executeQuery() 执行当前的查询,返回一个结果集对象
• setDate(int parameterIndex, Date x)向当前SQL语句中的指定位置绑定一个java.sql.Date值。
• setDouble(int parameterIndex, double x)向当前 SQL 语句中的指定位置绑定一个 double值
• setFloat(int parameterIndex, float x)向当前 SQL 语句中的指定位置绑定一个 float 值
• setInt(int parameterIndex, int x)向当前 SQL 语句中的指定位置绑定一个 int 值
• setString(int parameterIndex, String x)向当前 SQL 语句中的指定位置绑定一个 String 值
6 ResultSet 接口
ResultSet 提供检索不同类型字段的方法。
6.1常用方法
• getString(int index)、getString(String columnName)
获得在数据库里是 varchar、char 等类型的数据对象。
• getFloat(int index)、getFloat(String columnName)
获得在数据库里是 Float 类型的数据对象。
• getDate(int index)、getDate(String columnName)
获得在数据库里是 Date 类型的数据。
• getBoolean(int index)、getBoolean(String columnName)
获得在数据库里是 Boolean 类型的数据。
• getObject(int index)、getObject(String columnName)
获取在数据库里任意类型的数据。
6.2 ResultSet 对结果集进行滚动的方法
• next():移动到下一行。
• Previous():移动到前一行。
• absolute(int row):移动到指定行。
• beforeFirst():移动 resultSet 的最前面。
• afterLast() :移动到 resultSet 的最后面。
7 CallableStatement 接口
继承自 PreparedStatement 接口,由方法 prepareCall 创建,用于调用数据库的存储过程。
三、 JDBC 的使用
1)加载数据库驱动程序
2) 建立数据库连接 Connection
3) 创建执行 SQL 的语句Statement
4) 处理执行结果 ResultSet
5) 释放资源
1 ResultSet
注意 ResultSet 中封装的并不是我们查询到的所有的结果集,而是返回了查询到的结果集的数据库游标。通过 ResultSet 中的 next()方法操作游标的位置获取结果集。
1.2通过 ResultSet 实现逻辑分页
//查询 departments 表中的所有的数据,并且通过 ResultSet 实现逻辑分页
public void selectDeptPage(int currentPage,int pageRows){
Connection conn = null;
Statement state = null;
ResultSet rs = null;
try{
conn = JdbcUtil.getConnection();
state = conn.createStatement();
String sql = "select * from departments";
rs = state.executeQuery(sql);
//开始位置与结束位置
int begin = (currentPage -1)*pageRows;
int end = currentPage * pageRows;
//当前位置的计数器
int currentNum = 0;
while(rs.next()){
//什么情况下获取结果集中的数据
if(currentNum >= begin && currentNum < end){
System.out.println(rs.getInt("department_id")+"
"+rs.getString("department_name"));
//结束操作 ResultSet 的边界条件
if(currentNum == end -1){
break;
}
}
currentNum++;
}
}catch(Exception e){
e.printStackTrace();
}finally{
JdbcUtil.closeResource(state, conn, rs);
}
}
1.2什么是 SQL 注入
所谓 SQL 注入,就是通过把含有 SQL 语句片段的参数插入到需要执行的 SQL 语句中,最终达到欺骗数据库服务器执行恶意操作的 SQL 命令。
2. PreparedStatement 对象的使用
2.1 PreparedStatement 特点:
• PreparedStatement 接口继承 Statement 接口
• PreparedStatement 效率高于 Statement • PreparedStatement 支持动态绑定参数
• PreparedStatement 具备 SQL 语句预编译能力
• 使用 PreparedStatement 可防止出现 SQL 注入问题
2.2开始数据库的日志
show VARIABLES like '%general_log%'
set GLOBAL general_log = on
set GLOBAL log_output='table'
3.批处理操作
批处理:在与数据库的一次连接中,批量的执行条 SQL 语句
//批量添加
public void addBatch(List<Student> list){
Connection conn = null;
PreparedStatement ps = null;
try{
conn = JdbcUtil.getConnection();
ps = conn.prepareStatement("insert into test values(default,?,?)");
for(int i=0;i<list.size();i++){
ps.setString(1,list.get(i).getName());
ps.setInt(2, list.get(i).getId());
//添加批处理
ps.addBatch();
}
int[] arr =ps.executeBatch();
for(int i=0;i<arr.length;i++){
System.out.println(i);
}
}catch(Exception e){
e.printStackTrace();
}finally{
JdbcUtil.close(ps, conn, null);
}
}
4 动态查询
动态查询:根据用户给定的条件来决定执行什么样的查询。
// 动态查询
public List<Departments> selectDeptByProperty(Departments departments) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
List<Departments> list = new ArrayList<>();
try {
conn = JdbcUtil.getConnection();
String sql = genSQL(departments);
System.out.println(sql);
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
while (rs.next()) {
Departments dept = new Departments();
dept.setDepartmentId(rs.getInt("department_id"));
dept.setDepartmentName(rs.getString("department_name"));
dept.setLocationId(rs.getInt("location_id"));
list.add(dept);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtil.closeResource(ps, conn, rs);
}
return list;
}
// 拼接需要执行的 sql 语句
private String genSQL(Departments dept){
StringBuffer sb = new StringBuffer("select * from departments where 1=1 ");
if(dept.getDepartmentId() > 0){
sb.append(" and department_id =").append(dept.getDepartmentId());
}
if(dept.getDepartmentName() != null &&dept.getDepartmentName().length() > 0){
sb.append(" and department_name ='").append(dept.getDepartmentName()).append("'");
}
if(dept.getLocationId() > 0){
sb.append(" and location_id =").append(dept.getLocationId());
}
return sb.toString();
}
2 应用程序分层
2.1什么是应用程序分层
应用程序通过创建不同的包来实现项目的分层,将项目中的代码根据功能做具体划分,并存放在不同的包下。
2.2分层优点
1、分层结构将应用系统划分为若干层,每一层只解决问题的一部分,通过各层的协作提供整体解决方案。
2、分层结构具有良好的可扩展性,为应用系统的演化增长提供了一个灵活的支持,具有良好的可扩展性。增加新的功能时,无须对现有的代码做修改,业务逻辑可以得到最大限度的重用。
3、分层架构易于维护。在对系统进行分解后,不同的功能被封装在不同的层中,层与层之间的耦合显著降低。因此在修改某个层的代码时,只要不涉及层与层之间的接口,就不会对其他层造成严重影响。
2.3三层结构
三层结构就是将整个业务应用划分为:界面层(User Interface layer)、业务逻辑层(Business Logic Layer)、数据访问层(Data access layer)。区分层次的目的即为了“高内聚低耦合”的思想。在软件体系架构设计中,分层式结构是最常见,也是最重要的一种结构。
2.4项目分层
com.sxt.commons :工具类
com.sxt.dao :数据访问接口
com.sxt.dao.impl:数据访问接口的实现类
com.sxt.pojo:实体类
com.sxt.service:服务层,业务逻辑接口
com.sxt.service.impl:业务逻辑实现类
com.sxt.test:测试类
2.5 使用步骤
1).先创建好需要的各个包进行分层,导入所需的jar包,
2).创建.properties文件用于存放一些信息,用于JdbcUtil工具类的创建
3).在commons包下创建JdbcUtil工具类
4).在com.sxt.pojo包下创建实体类,封装与数据表相关的数据(实体类的类名与表名相同,属性名称与表的字段相同)
5).在dao包(持久层/数据访问层)下编写dao接口(表名+Dao)及其实现类(表名+DaoImpl)
6.) service包(服务层/业务逻辑层),调用Dao完成业务逻辑。新建Service接口(表名+Service)及其Service接口的实现类(表名+ServiceImpl),并调用dao.impl包下的方法。
7).最后在test包下进行测试
3. 封装通用的 BaseDao
3.1 封装DML主要用到了
ParameterMetaData pmd = psmt.getParameterMetaData();//获取占位符的元数据
int paramCount = pmd.getParameterCount();//获取语句中占位符的数量
3.2封装查询操作
注意点:
完成通用查询的方法
* <T> List<T> :泛型方法
* 注意:通用的查询方法中要求模型对象的属性名必须要和数据库表中的列名相同。
主要用到了:ResultSetMetaData rsmd = rs.getMetaData();//获取结果集的信息
rsmd.getColumnCount()//列的数量
String columnName = rsmd.getColumnName(i+1);//得到列名
Object value = rs.getObject(columnName);//获取列的值
BeanUtils.setProperty(bean, columnName, value);//通过BeanUtil工具类将值当如到对象中
package com.sxt.commons;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import org.apache.commons.beanutils.BeanUtils;
public class JdbcUtil {
//存储资源文件中的信息
private static String driver;
private static String url;
private static String username;
private static String password;
private static Connection conn;//连接
private static PreparedStatement psmt;
private static ResultSet rs;
/**
* 读取src下的jdbc.properties资源文件
*/
static{
ResourceBundle rb = ResourceBundle.getBundle("jdbc");//基础名称
driver = rb.getString("driver");
url = rb.getString("url");
username = rb.getString("username");
password = rb.getString("password");
}
/**
* 获取连接的方法
*/
public static void getConnection(){
try {
//加载驱动
Class.forName(driver);
conn = DriverManager.getConnection(url, username, password);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 释放资源
*/
public static void closeAll(){
try {
if(rs!=null){
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(psmt!=null){
psmt.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(conn!=null){
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 封装执行增、删、改的方法
* @param sql insert into student(scode,sname,age,score) values(?,?,?,?)
* @param params {"001", "zhangsan", 20, 80}
* @return
*/
public static int executeDML(String sql,Object... params){
int count=0;
getConnection();//获取连接
try {
psmt = conn.prepareStatement(sql);//创建PreparedStatement对象
//如果语句中存在占位符,则需要为占位符绑定值
ParameterMetaData pmd = psmt.getParameterMetaData();//获取占位符的元数据
int paramCount = pmd.getParameterCount();//获取语句中占位符的数量
//使用循环为占位符绑定值,占位符的下标从1开始,数组的下标从零开始
for(int i=0;i<paramCount;i++){
psmt.setObject(i+1, params[i]);//psmt.setObject(1,params[0]);psmt.setObject(2,params[1])..
}
count = psmt.executeUpdate();//执行SQL语句
} catch (SQLException e) {
e.printStackTrace();
}finally{
closeAll();
}
return count;
}
/**
* 封装查询操作
* @param sql:需要执行的查询SQL语句
* @param T:泛型,将查询的记录封装的对象类型
* @param params:占位符对应的参数值
* @return
*/
public static <T>List<T> executeDQL(String sql,Class<T> clazz,Object... params){
List<T> list = new ArrayList<T>();//创建集合
getConnection();//获取连接
try {
psmt = conn.prepareStatement(sql);//创建PreparedStatement对象
//如果语句中存在占位符,则需要为占位符绑定值
ParameterMetaData pmd = psmt.getParameterMetaData();//获取占位符的元数据
int paramCount = pmd.getParameterCount();//获取语句中占位符的数量
//使用循环为占位符绑定值,占位符的下标从1开始,数组的下标从零开始
for(int i=0;i<paramCount;i++){
psmt.setObject(i+1, params[i]);//psmt.setObject(1,params[0]);psmt.setObject(2,params[1])..
}
rs = psmt.executeQuery();//执行SQL语句
//遍历结果集,将一条记录封装成对象,并添加到list中
while(rs.next()){
T bean = clazz.newInstance();//通过反射机制动态创建对象:Student student = new Student();
ResultSetMetaData rsmd = rs.getMetaData();//获取结果集的元数据(包含列名,列的数量...)
int columnCount = rsmd.getColumnCount();//获取结果集中列的数量
for(int i=0;i<columnCount;i++){
String columnName = rsmd.getColumnName(i+1);//获取第i列的列名
Object value = rs.getObject(columnName);//通过列名获取该列的值
//使用apache的beanutils组件利用反射机制动态调用属性的setter方法进行赋值
//要求列名必须和属性名称相同
BeanUtils.setProperty(bean, columnName, value);//第一个参数代表回调的对象,第二个参数代表该对象的属性名称,第三个参数代表属性的值
}
//将封装数据的对象添加到list中
list.add(bean);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
closeAll();
}
return list;
}
}