目录
SQL注入问题
JDBC原理
JDBC标准
JDBC是什么
Java Database Connectivity : Java访问数据库的解决方案
希望用相同的方式访问不同的数据库,以实现与具体数据库无关的Java操作界面,JDBC定义一套标准接口,即访问数据库的通用API ,不同的数据库厂商根据各自数据库的特点去实现这些接口.
JavaDataBaseConnectivity:Java数据库连接,是Sun公司提供的一套和数据库进行连接的API(Application Program Interface应用程序编程接口), 作用:通过Java语言和数据库软件进行连接
使用JDBC优点
在工作中Java程序员有可能连接多种不同的数据库,为了避免Java程序员每一种数据库都学习一套新的方法,Sun公司定了一套方法的声明(JDBC),把方法名固定,不管连接的是什么数据库方法名是一样的,各个数据库厂商根据方法名写方法的实现类(驱动),这样Java程序员只需要掌握JDBC中方法的调用,即可访问任何数据库,而且安装JDBC规范所写的代码就算是换数据库代码一行都不用改.
JDBC接 口及数据库厂商实现
![](https://img-blog.csdnimg.cn/20200615202129351.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2MyMDIwMDM=,size_16,color_FFFFFF,t_70)
JDBC工作原理
-
JDBC定义接口
-
数据库厂商实现接口
-
程序员调用接口, 实际调用的是底层数据库厂商的实现部分
-
JDBC工作过程:
- 加载驱动,建立连接
- 创建语句对象
- 执行SQL语句
- 处理结果集
- 关闭连接
package cn.tedu;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
/**
*
* @author Administrator
*
*/
public class Demo01 {
public static void main(String[] args) throws Exception {
//1.注册驱动:高速驱动编译器使用的是什么数据库
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取在数据库连接 导包:java.sql
//jdbc:mysql://localhost:3306/mysql?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/newdb3?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true", "root", "root");
System.out.println(conn);
//3.创建执行SQL 语句的对象
Statement s = conn.createStatement();
//4.执行SQL
String sql = "create table jdbct1(id int,name varchar(10))";
s.execute(sql);
//5.关闭资源
conn.close();
System.out.println("执行完成!");
}
}
Driver ( 驱动程序)接口及驱动类加载
![](https://img-blog.csdnimg.cn/20200615202516941.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2MyMDIwMDM=,size_16,color_FFFFFF,t_70)
Connection( 连接,关联)接口
Class. forName (" com. mysq1. jdbc . Driver");
Connection conn =
DriverManager . getConnection("jdbc :mysq1 ://1oc alhost :3306/ demo",
root" ,
);
// NOTE:Connection只是接口 !真正的实现是由数据库厂商提供的驱动包完成的
/**根据url连接参数找到与之匹配的
Driver对象,调用其方法获取连接*/
Statement( 声明)接口
执行SQL语句的对象Statement
- execute(sql) 可以执行任意SQL语句,但是推荐执行数据库和表相关的SQL(DDL数据定义语言)(execute 执行 )
- int row = executeUpdate(sql); 此方法执行增insert删delete改update的SQL ,方法的返回值为生效的行数( executeUpdate 执行更新) (int 类型 )
- ResultSet rs = executeQuery(sql); 此方法执行查询的SQL语句,返回值是结果集对象,里面装着查询回来的结果(executeQuery 执行查询)
![](https://img-blog.csdnimg.cn/20200615202731348.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2MyMDIwMDM=,size_16,color_FFFFFF,t_70)
ResultSet接口
执行查询SQL语句后返回的结果集,由ResultSet接口接收,常用处理方式:遍历/判断是否有结果(登录)
String sql =
”
select * from emp";
ResultSet rs = stmt . executeQuery(sq1);
while (rs.next()) {
System . out . println(rs . getInt( " empno")+","
+rs. getstring(" ename"));
}
- 查询的结果存放在ResultSet对象的一系列行中
- ResultSet对象的最初位置在行首
- ResultSet.next(方法用来在行间移动
- ResultSet.getXXX0方法用来取得字段的内容
public static void main(String[] args) throws Exception {
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/newdb3?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true",
"root", "root");
Statement s = conn.createStatement();
String sql = "select ename,sal from emp";
//执行查询 得到结果及 集对象
ResultSet rs = s.executeQuery(sql);
//遍历结果 集对象 中的查询结果
long t = System.currentTimeMillis();
while(rs.next()) {
//参数为字段名
// String name = rs.getString("ename");
//通过参数为字段位置
String name= rs.getString(1);
// double sal = rs.getDouble("sal");
double sal = rs.getDouble(2);
System.out.println(name+": "+sal);
}
long te = System.currentTimeMillis()-t;
System.out.println(te);
System.out.println("执行完成!");
}
}
数据库厂商实现
Oracle实现
下载对应数据库的驱动
一ojdbc6jar / ojdbc14.jar
将驱动类导入到项目中
Maven
加载驱动类
- Class.forName(" orace.jdbc.OracleDriver")
MySQL实现
下载对应数据库的驱动
- mysql-connectorjava-5.0.4-bin.jar
将驱动类导入到项目中
一Maven
加载驱动类
- Class.forName(" com.mysql.jdbc.Driver");
JDBC基础
连接管理
通过连接工具类获取连接
- 在工程中,编写一个访问数据库的工具类,此后所有访问数据库的操作,都从工具类中获取连接
- 两种方式:
- 直接把数据配置写在工具类中
- -把数据库配置写在一个properties属性文件里 ,工具类读取属性文件,逐行获取数据库参数(推荐)
读取配置文件
-
将需要改动的数据从代码中移到*.properties配置文件中,好处: 以后便于修改
-
如何读取配置文件里面的数据:
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/newdb3?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
db.username=root
db.password=root
db.maxActive=10
db.initialSize=2
通过属性文件维护连接属性
![](https://img-blog.csdnimg.cn/20200616231110845.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2MyMDIwMDM=,size_16,color_FFFFFF,t_70)
从类路径中加载属性文件
/ /属性文件所在的位置
String path =” com/ tarena/ dms/ daodemo/ v2/ db. properties" ;
//获得当前类的路径,加载指定属性文件
properties . load(DBUtility . class. getClassLoader( )
. getResourceAsstream(path));
连接的关闭
//在工具类中定义公共的关闭连接的方法
//所有访问数据库的应用,共享此方法
public static void closeConnection(Connection con) {
if(con != nu11) {
try {
con.close();
} catch (SQLException e) {
e. printstackTrace();
}
}
}
自动关闭
package cn.tedu;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Demo02 {
public static void main(String[] args) {
//获取连接
try (Connection conn = DBUtils.getConn();){
Statement s = conn.createStatement();
String sql = "select ename from emp";
ResultSet rs = s.executeQuery(sql);
while(rs.next()) {
String name = rs.getString(1);
System.out.println(name);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
数据库连接池DBCP
-
DataBaseConnectivityPool(数据库连接池)
-
为什么使用数据库连接池: 如果没有数据库连接池,每一个业务都至少对应一个数据库连接,如果有1万个业务至少有1万次连接的建立和断开,频繁的开关连接非常浪费资源,使用数据库连接池,可以将连接重用,使用完之后的连接并不是断开而是放回连接池中给其它业务使用,这样就避免了频繁开关链接,从而提高了执行效率
连接池技术
为什么要使用连接池
- 数据库连接的建立及关闭资源消耗巨大
- 传统数据库访问方式:一次数据库访问对应一个物理连接每次操作数据库都要打开、关闭该物理连接,系统性能严重受损
- 解决方案:数据库连接池( Connection Pool )
- 系统初始运行时,主动建立足够的连接,组成一个池。每次应用程序请求数据库连接时, 无需重新打开连接,而是从池中取出已有的连接,使用完后,不再关闭,而是归还
连接池中连接的释放与使用原则
- 应用启动时,创建初始化数目的连接
- 当申请时无连接可用或者达到指定的最小连接数,按增量参数值
创建新的连接
为确保连接池中最小的连接数的策略:
- 动态检查:定时检查连接池,一旦发现数量小于最小连接数,则补充相应的新连接,保证连接池正常运转
- 静态检查:空闲连接不足时,系统才检测是否达到最小连接数按需分配,用过归还,空闲超时释放,获取超时报错
- 连接池也只是接口,具体实现由厂商来完成
Druid数据库连接池
利用Maven导入
<!-- 数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
通过DataSource获取连接
package cn.tedu;
import java.sql.Connection;
import java.sql.SQLException;
import com.alibaba.druid.pool.DruidDataSource;
public class Demo03 {
public static void main(String[] args) throws SQLException {
//创建数据库连接池对象
//如果使用的是DBCPjar包 创建 BasicDataSource
DruidDataSource ds = new DruidDataSource();
//设置数据库连接信息
ds.setDriverClassName("");
ds.setUrl("");
ds.setUsername("");
ds.setPassword("");
//设置初始连接数量
ds.setInitialSize(3);
//设置最大连接数量
ds.setMaxActive(5);
//从连接池中获取连接对象 异常抛出
Connection conn = ds.getConnection();
}
}
Apache DBCP数据库连接池
- DBCP(DataBase connection pool) :数据库连接池
- Apache的一个Java连接池开源项目,同时也是Tomcat使用的连接池组件
- 连接池是创建和管理连接的缓冲池技术,将连接准备好被任何需要它们的应用使用
需要两个jar包文件
- - commons-dbcp-1.4.jar 连接池的实现
- - commons-pool-1 5.jar连接池实现的依赖库
利用Maven导入
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
通过DataSource获取连接
private static BasicDataSource dataSource
= new BasicDataSource );
dataSource . setDr iverClassName ( dri veClas sName) ;
dataSource . setUrl(ur1);
dataSource . setusername( username) ;
dataSource . setPassword(password);
Connection conn = dataSource . getConnection();
连接池参数
- 常用参数有:
- 初始连接数
- 最大连接数
- 最小连接数
- 每次增加的连接数
- 超时时间
- 最大空闲连接
- 最小空闲连接
- 根据应用需要,设置合适的值
package cn.tedu;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import com.alibaba.druid.pool.DruidDataSource;
public class DBUtils {
private static DruidDataSource ds;
static {
//创建读取*.properties配置文件的对象
Properties p = new Properties();
//获取文件的输入流 通过反射的方式得到文件的输入流
//这种写法会自动去src/main/resources目录下查找文件
InputStream ips =
DBUtils.class.getClassLoader()
.getResourceAsStream("jdbc.properties");
//让文件和读取配置文件的对象关联
try {
p.load(ips);
} catch (IOException e) {
e.printStackTrace();
}
//读取配置文件中的数据
String url = p.getProperty("db.url");
String username = p.getProperty("db.username");
String password = p.getProperty("db.password");
String driver = p.getProperty("db.driver");
//创建数据库连接池对象
//如果使用的是DBCPjar包 创建 BasicDataSource
ds = new DruidDataSource();
//设置数据库连接信息
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
//设置初始连接数量
ds.setInitialSize(3);
//设置最大连接数量
ds.setMaxActive(5);
}
public static Connection getConn() throws SQLException {
//从连接池中获取连接对象 异常抛出
Connection conn = ds.getConnection();
return conn;
}
}
异常处理
SQLException简介
-
java.sql.SQL Exception是在处理JDBC时常见的exception对象
-
用来表示JDBC操作过程中发生的具体错误
- 一般的SQLException都是因为操作数据库时出错,比如Sq|语句写错,或者数据库中的表或数据出错
- 常见异常:
- 登录被拒绝,可能原因:程序里取键值对信息时的大小写和属性文件中不匹配
- 列名无效,可能原因:查找的表和查找的列不匹配
- 无效字符,可能原因: sq|语句语法有错,比如语句结尾时不能有分号
- 无法转换为内部表示,可能原因:结果集取数据时注意数据类型
- 表或者视图不存在检查SQL中的表名是否正确
- 不能将空值插入,检查执行insert操作时,是否表有NOT NULL约束,而没有给出数据
- 缺少表达式检查SQL语句的语法
- SQL命令未正确结束,检查SQL语句的语法
- 无效数值企图将字符串类型的值填入数字型而造成,检查SQL语句
- 文件找不到,可能原因: db.properties文件路径不正确
处理SQL Exception
SQL Exception属于Checked Exception,必须使用ry..catch或throws明确处理
public static synchronized Connection getConnection() throws
SQLException {
/ /语句
}
try {
//语句
} catch (SQLException e) {
e . printstackTrace();//追踪处理
//throw new Runt imeException(e);//或者抛出
}
JDBC核心API:
Statement
用于执行静态 SQL 语句并返回它所生成结果的对象。
Statement执行查询
创建Statement的方式:
Connection.createStatement( );
执行INSERT, UPDATE和DELETE :
Statement.executeUpdate( )
执行SELECT :
Statement.executeQuery( )
package cn.tedu;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Demo04 {
public static void main(String[] args) throws SQLException {
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/newdb3?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true","root", "root");
Statement s = conn.createStatement();
String sql = "select ename,sal from emp";
//执行查询 得到结果集对象
ResultSet rs = s.executeQuery(sql);
//遍历结果集对象中的查询结果
while(rs.next()) {
//参数为字段名
//String name = rs.getString("ename");
//参数为字段位置
String name = rs.getString(1);
double sal = rs.getDouble(2);
System.out.println(name+":"+sal);
}
conn.close();
}
}
Statement执行新增
package cn.tedu;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class Demo02 {
public static void main(String[] args) throws SQLException {
//2. 获取数据库连接 导包:java.sql
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/newdb3?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true",
"root", "root");
Statement s = conn.createStatement();
String sql = "insert into jdbct1 values(1,'刘德华')";
//执行插入数据的SQL
s.executeUpdate(sql);
conn.close();
System.out.println("执行完成!");
}
}
Statement执行修改
package cn.tedu;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class Demo03 {
public static void main(String[] args) throws SQLException {
//2. 获取数据库连接 导包:java.sql
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/newdb3?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true","root", "root");
Statement s = conn.createStatement();
String sql = "update jdbct1 set name='张学友' where id=1";
//执行修改/删除数据的SQL
s.executeUpdate(sql);
conn.close();
System.out.println("执行完成!");
}
}
Statement执行删除
package cn.tedu;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class Demo03 {
public static void main(String[] args) throws SQLException {
//2. 获取数据库连接 导包:java.sql
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/newdb3?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true","root", "root");
Statement s = conn.createStatement();
String sql = "delete from jdbct1 where id=1";
//执行修改/删除数据的SQL
s.executeUpdate(sql);
conn.close();
System.out.println("执行完成!");
}
}
PreparedStatement
表示预编译的 SQL 语句的对象。
PreparedStatement原理
- Statement主要用于执行静态SQL语句,即内容固定不变的SQL语句
- Statement每执行一次都要对传 入的SQL语句编译次,效率较差
- 某些情况下, SQL语句只是其中的参数有所不同,其余子句完全相同,适用于PreparedStatement
- 预防sq|注入攻击
预编译的SQL执行对象PreparedStatement
- SQL注入问题: SQL注入指在传递值得地方传递进去了SQL语句,使原有的SQL业务逻辑发生改变.
- 使用PreparedStatement可以避免SQL注入,因为在创建对象时就已经将业务逻辑锁死,用户输入的内容不会影响原有逻辑
- 使用PreparedStatement可以避免进行SQL语句的拼接.
- 注入内容: ' or '1'='1
- PreparedStatement实例包含已事先编译的SQL语句
- SQL语句可有一个或多个IN参数
- IN参数的值在SQL语句创建时未被指定。该语句为每个IN参数保留一个问号(“?")作为占位符
- 每个问号的值必须在该语句执行之前,通过适当的setInt或者setString方法提供。
- 由于PreparedStatement对象已预编译过,所以其执行速度要快于Statement对象。因此,多次执行的 SQL语句经常创建PreparedStatement对象,以提高效率。
- 批量处理
- executeUpdate或executeQuery方法每次执行都需要传递一次网络数据, 批量操作可以将多次网络数据传输合并成一次,从而提高执行效率
- 代码参考Demo06.java
package cn.tedu;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
public class Demo06 {
public static void main(String[] args) {
//获取数据库连接
try (Connection conn = DBUtils.getConn();) {
// String sql1 = "insert into user values(null,'刘备','123456')";
// String sql2 = "insert into user values(null,'关羽','123123')";
// String sql3 = "insert into user values(null,'张飞','456123')";
// Statement s = conn.createStatement();
// //非批量操作
s.executeUpdate(sql1);
s.executeUpdate(sql2);
s.executeUpdate(sql3);
// //批量操作
// s.addBatch(sql1);
// s.addBatch(sql2);
// s.addBatch(sql3);
// //执行批量操作
// s.executeBatch();
// System.out.println("执行完成!");
//PreparedStatement的批量操作
String sql = "insert into user values(null,?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
for (int i = 1; i <= 100; i++) {
ps.setString(1, "name"+i);
ps.setString(2, "pwd"+i);
//添加到批量操作
ps.addBatch();
//每隔20次执行一次 避免内存溢出
if(i%20==0) {
//执行批量操作
ps.executeBatch();
}
}
//执行批量操作 总次数是20的倍数时 这行代码可删除
ps.executeBatch();
System.out.println("执行完成!");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
如果SQL语句中有变量则使用PreparedStatement,没有变量则用Statement
提升性能
- 数据库具备缓存功能,可以对statement的执行计划进行缓存,以免重复分析
- 缓存原理:
- 使用statement本身作为key并将执行计划存入与statement对应的缓存中
- 对曾经执行过的statements , 再运行时执行计划将重用
举例:
- SELECT a, b FROM t WHEREc= 1 ;
- 再次发送相同的statement时, 数据库会对先前使用过的执行计划进行重用,降低开销
悲剧:
- SELECT a,b FROM t WHEREc= 1 ;
- SELECT a, b FROM t WHEREc= 2 ;
- 被视作不同的SQL语句,执行计划不可重用
- 提升性能 SELECT a, b FROM t WHEREc = ?";
- 被视作同一个的SQL语句执行计划可重用
![](https://img-blog.csdnimg.cn/20200616235125700.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2MyMDIwMDM=,size_16,color_FFFFFF,t_70)
SQL Injection简介
//场景:
String sql = "select * from t where username= '" + name + "'and password='""+ passwd + "'";
//输入参数后,数据库接受到的完整sq|语句将是:
select * from t where username = 'scott' and password ='tiger';
![](https://img-blog.csdnimg.cn/20200616235510492.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2MyMDIwMDM=,size_16,color_FFFFFF,t_70)
防止SQL Injection (注入)
- 对JDBC而言, SQL注入攻击只对Statement有效,对PreparedStatement无效,因为PreparedStatement不允许在插入参数时改变SQL语句的逻辑结构
- 使用预编译的语句对象,用户传入的任何数据不会和原SQL语句发生匹配关系,无需对输入的数据做过滤
- 如果用户将"or1 = 1"传入赋值给占位符, SQL语句将无法执行
- select * from t where username = ? and password = ?;
小练习:注册登录
注册
package cn.tedu;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
public class Demo04 {
public static void main(String[] args) {
//从控制台获取输入的用户名和密码
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名");
String username = sc.nextLine();
System.out.println("请输入密码");
String password = sc.nextLine();
sc.close();
//获取连接
try (Connection conn = DBUtils.getConn();){
String sql =
"insert into user values(null,'"
+username+"','"+password+"')";
Statement s = conn.createStatement();
s.executeUpdate(sql);
System.out.println("注册成功!");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
登录:
package cn.tedu;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
public class Demo05 {
public static void main(String[] args) {
//从控制台获取输入的用户名和密码
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名");
String username = sc.nextLine();
System.out.println("请输入密码");
String password = sc.nextLine();
sc.close();
//获取数据库连接
try (Connection conn = DBUtils.getConn();) {
//创建登录的SQL语句 把用户输入的内容拼接进去
// String sql =
// "select count(*) from user where username='"
// +username+"' and password='"+password+"'";
// System.out.println(sql);
// Statement s = conn.createStatement();
// ResultSet rs = s.executeQuery(sql);
//解决SQL注入写法
String sql =
"select count(*) from user where username=? and password=?";
//创建预编译的SQL执行对象
//预编译SQL执行对象好处:在创建对象时就将SQL语句业务逻辑锁死
//不会被用户输入的内容导致改变
PreparedStatement ps = conn.prepareStatement(sql);
//替换SQL语句中的? 参数1:第几个问号 参数2:替换的内容
ps.setString(1, username);
ps.setString(2, password);
//执行时切记不能传递SQL语句
ResultSet rs = ps.executeQuery();
//因为查询的结果只有一个数 直接调用next方法
rs.next();
//1代表结果集中第一个字段值
int count = rs.getInt(1);
if(count>0) {//登录成功
System.out.println("登录成功!");
}else {//登录失败
System.out.println("登录失败!");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
ResultSet (结果集)
ResultSet,数据库结果集的数据表,通常通过执行查询数据库的语句生成。
结构化查询语言
ResultSet 对象具有指向其当前数据行的指针。最初,指针被置于第一行之前。next 方法将指针移动到下一行;因为该方法在 ResultSet 对象中没有下一行时返回 false,所以可以在 while 循环中使用它来迭代结果集。
默认的 ResultSet 对象不可更新,仅有一个向前移动的指针。因此,只能迭代它一次,并且只能按从第一行到最后一行的顺序进行。可以生成可滚动和/或可更新的 ResultSet 对象。以下代码片段(其中 con 为有效的 Connection 对象)演示了如何生成可滚动且不受其他更新影响的、可更新的结果集。请参阅 ResultSet 字段以了解其他选项。
当生成 ResultSet 对象的 Statement 对象关闭、重新执行或用来从多个结果的序列检索下一个结果时,ResultSet 对象会自动关闭。
结果集遍历
//常用的遍历方式:
string sql = "select empno, ename, sal, hiredate from emp" ;
rs = stmt. executeQuery(sq1);
while ( rs.next() ) {
int empno = rs. getInt( "empno") ;
string ename = rs. getstring(" ename") ;
double sal = rs. getDouble("sal");
Date hiredate = rs . getDate( "hiredate" );
}
rs.close( );
ResultSetMeta Data(结果集元数据)
- ResultSetMetaData:数据结果集的元数据
- 和查询出来的结果集相关,从结果集(ResultSet)中获取
package cn.tedu;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;
public class Demo07 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入查询的页数");
int page = sc.nextInt();
System.out.println("请输入查询的条数");
int count = sc.nextInt();
sc.close();
//获取数据库连接
try (Connection conn = DBUtils.getConn();) {
String sql = "select * from user limit ?,?";
PreparedStatement ps = conn.prepareStatement(sql);
//替换? 跳过的条数=(请求页数-1)*每页条数
ps.setInt(1, (page-1)*count);
ps.setInt(2, count);
ResultSet rs = ps.executeQuery();
//遍历结果集中的多条数据
while(rs.next()) {
//表字段 id,username,password
String name = rs.getString(2);
System.out.println(name);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
JDBC高级编程
事务处理
事务简介
- 事务( Transaction ) : 数据库中保证交易可靠的机制
- JDBC支持数据库中的事务概念
- 在JDBC中, 事务默认是自动提交的
事务特性ACID :
- 原子性( Atomicity ) : 事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行
- 一致性( Consistency ) : 事务在完成时,必须使所有的数据都保持一致状态
- -隔离性(Isolation ) :由并发事务所作的修改必须与任何其它并发事务所作的修改隔离
- -持久性( Durability ) :事务完成之后,它对于系统的影响是永久性的
- 事务是数据库的概念, JDBC支持事务,本质还是在数据库中实现的
![](https://img-blog.csdnimg.cn/20200617185601742.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2MyMDIwMDM=,size_16,color_FFFFFF,t_70)
JDBC事务API
相关API :
- Connection.getAutoCommit0):获得当前事务的提交方式,默认为true
- Connection.setAutoCommit):设置事务的提交属性,参数是 true :自动提交; false :不自动提交
- Connection.commit():提交事务
- Connection.rollback0:回滚事务
JDBC标准事务编程模式
try{
autoCommit = con. getAutoCommit(); // 1. 获得自动提交状态
con. setAutoCommit(false); // 2.关闭自动提交
stmt . executeUpdate(sq11); // 3.执行SQL语句
stmt . executeUpdate(sq12);
con. commit(); // 4.提交
con . setAutoCommit( autoCommit); // 5.将自动提交功能恢复到原来的状态
}catch(SQLException e){
conn. rollback();//异常时回滚
}
批量更新
批量更新的优势
- 批处理:发送到数据库作为一个单元执行的一组更新语句
- 批处理降低了应用程序和数据库之间的网络调用
- 相比单个SQL语句的处理,批处理更为有效
批量更新API
- addBatch(String sql)--Statement类的方法,可以将多条sq|语句添加Statement对象的SQL语句列表中
- addBatch( )-- PreparedStatement类的方法, 可以将多条预编译的sq|语句添加到PreparedStatement对象的SQL语句列表中
- executeBatch( )--把Statement对象或PreparedStatement对象语 句列表中的所有SQL语句发送给数据库进行处理
- clearBatch( )清空当前SQL语句列表
防止OutOfMemory
- 如果Preparedstatement对象中的SQL列表包含过多的待处理SQL语句,可能会产生OutOfMemory错误
- 及时处理SQL语句列表
for(inti=0;i<1000;i++){
sql =“insert into emp( empno, ename)
values(emp_ seq. nextval,' name"+i+"'")";
stmt . addBatch(sq1);将SQL语句加入到Batch中
if(i%500==0){
stmt . executeBatch(); //及时处理
stmt . clearBatch(); //清空列表
}
}
//最后一次列表不足500条,处理
s tmt . executeBatch( );
批量插入练习
package cn.tedu;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
public class Demo06 {
public static void main(String[] args) {
//获取数据库连接
try (Connection conn = DBUtils.getConn();) {
// String sql1 = "insert into user values(null,'刘备','123456')";
// String sql2 = "insert into user values(null,'关羽','123456')";
// String sql3 = "insert into user values(null,'张飞','123456')";
// //如果SQL 语句中没有变量 用Statement
// Statement s = conn.createStatement();
// //非批量操作
s.executeQuery(sql1);
s.executeQuery(sql2);
s.executeQuery(sql3);
// /*
// * executeUpdate 执行更新
// * executeQuery 执行查询
// * addBatch 添加 批处理
// */
//
// //批量操作
// s.addBatch(sql1);
// s.addBatch(sql2);
// s.addBatch(sql3);
// //执行批量操作
// s.executeBatch();
// System.out.println("执行完成!");
//PreparedStatement 的批量操作
String sql ="insert into user values(null,?,?)";
PreparedStatement ps= conn.prepareStatement(sql);
for (int i = 1; i <= 100; i++) {
ps.setString(1, "name"+i);
ps.setString(2, "pew"+i);
//添加到批量操作
ps.addBatch();
//没隔20次执行一次 避免内存溢出
if(i%20==0) {
ps.executeBatch();
}
}
//执行批量操作 总次数是20的倍数这行代码可以删除
ps.executeBatch();
System.out.println("执行完成!");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
返回自动主键
获取自增主键值:
关联数据插入
![](https://img-blog.csdnimg.cn/20200617000529515.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2MyMDIwMDM=,size_16,color_FFFFFF,t_70)
通过自增类型产生主键
CREATE TABLE animals (
id INT NOT NULL AUTO_ INCREMENT ,
name CHAR(30) NOT NULL ,
PRIMARY KEY (id)
);
INSERT INTO animals (name )
VALUES('dog' ),('cat' ),(' penguin'),('1ax'),( 'whale' ),(' ostrich');
SELECT * FROM animals;
JDBC返回自动主键API
package cn.tedu;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Demo08 {
public static void main(String[] args) {
//获取数据库连接
try (Connection conn = DBUtils.getConn();) {
String sql = "insert into user values(null,'刘德华','50')";
Statement s = conn.createStatement();
//传递参数 设置获取自增主键值
s.execute(sql,Statement.RETURN_GENERATED_KEYS);
System.out.println("执行完成!");
//获取自增主键值
ResultSet rs = s.getGeneratedKeys();
//移动游标
rs.next();
//因为获取的结果集中只有一个数据所以写1
int id = rs.getInt(1);
System.out.println(id);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
练习:
package cn.tedu;
import java.lang.Thread.State;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
public class Demo10 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入球队名称");
String teamName = sc.nextLine();
System.out.println("请输入球员名称");
String playerName = sc.nextLine();
sc.close();
//获取数据库连接
try (Connection conn = DBUtils.getConn();) {
//先查询 没有插入得到自增teamId 有的话获取出teamId
String sql = "select id from team where name=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, teamName);
ResultSet rs = ps.executeQuery();
int teamId;
//判断是否查询到
if(rs.next()) {//查询到了 之前保存过
teamId = rs.getInt(1);
System.out.println("之前保存过球队");
}else {//之前没有存过
String sql1 = "insert into team values(null,?)";
PreparedStatement ps1 = conn.prepareStatement(sql1
,Statement.RETURN_GENERATED_KEYS);
ps1.setString(1, teamName);
ps1.executeUpdate();
//获取自增主键值
ResultSet rs1 = ps1.getGeneratedKeys();
rs1.next();
teamId = rs1.getInt(1);
System.out.println("球队保存完成!");
}
//保存球员
String sql2 = "insert into player values(null,?,?)";
PreparedStatement ps2 = conn.prepareStatement(sql2);
ps2.setString(1, playerName);
ps2.setInt(2, teamId);
ps2.executeUpdate();
System.out.println("保存球员完成");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
DAO
什么是DAO
DAO封装对数据的访问
- DAO (Data Access Object)数据访问对象
- 建立在数据库和业务层之间,封装所有对数据库的访问
- 目的:数据访问逻辑和业务逻辑分开
- 为了建立一个健壮的Java应用,需将所有对数据源的访问操作抽象封装在一个公共API中,包括:
- 建立一个接口,接口中定义了应用程序中将会用到的所有事务方法
- 建立接口的实现类,实现接口对应的所有方法,和数据库直接交互
- 在应用程序中,当需要和数据源交互时则使用DAO接口,不涉及任何数据库的具体操作
- DAO通常包括:
- 一个DAOIN类;
- 一个DAO接口;
- 一个实现DAO接口的具体类;
- 数据传递对象(实体对象或值对象);
实体对象
- DAO层需要定义对数据库中表的访问
- 对象关系映射(ORM : Object/Relation Mapping)描述对象和数据表之间的映射,将Java程序中的对象对应到关系数据库的表中
- 表和类对应
- 表中的字段和类的属性对应
- 记录和对象对应
编写DAO
查询方法
public Account findById(Integer id) throws SQLException {
Connection conn = getConnection();
String sql = SELECT BY_ID; //预先定 义好的SQL查询语句
Preparedstatement ps = conn. preparestatement(sq1);
ps.setInt(1, id);//传入参数
ResultSet rs = ps . executeQuery();
Account account = null ;
while(rs .next()){//处理结果集
account=new Account( );
account . setId(rs. getInt( "ACCOUNT_ ID"));
//设置account对象所有的属性,略
}
return ac count ;
}
更新方法
public boolean update( Account account) throws SQLException {
Connection conn = getConnection();
String sql = UPDATE_STATUS; //预先定义好的SQL语句
Preparedstatement ps = conn.prepareStatement(sq1);
ps.setInt(1, account. getId());//传入参数
ps.setstring(2, account.getstatus() );
int flag= ps.executeUpdate();
return (flag>0 ? true : false);
}
异常处理机制
多层系统的异常处理原则:
- 谁抛出的异常,谁捕捉处理,因为只有异常抛出者,知道怎样捕捉处理异常
- 尽量在当前层中捕捉处理抛出的异常,尽量不要抛出到上层接口
- 尽量在每层中封装每层的异常类,这样可准确定位异常抛出的层
- 如异常无法捕捉处理,则向上层接口]抛出,直至抛给JVM
- 应尽量避免将异常抛给JVM