JAVA EE-JDBC
塑成一个雕像,把生命赋给这个雕像,这是美丽的;创造一个有智慧的人,把真理灌输给他,这就更美丽。 —— 雨果
什么是JDBC?
- JDBC 就是由 java提供的一套访问数据库的统一api. 使用这套api , 我们在 切换库时 十分方便. 并且切换库不会改变代码.学习成本也降低了.
如何开发一个JDBC程序?
1 导包 ==> 导入厂商提供的数据库驱动. ==> mysql-connector-java-5.0.8-bin.jar
2> 注册驱动
3> 连接数据库
4> 操作数据库(执行sql)
5> 关闭资源
JDBC中的类
DriverManager 用于注册驱动,获得连接
Connection 代表连接 , 获得Statement对象
Statement 运送sql语句
ResultSet 将运行结果从数据库运回java端
一个简单的JDBC连接实例
public class Demo {
@Test
//发送插入语句
public void fun1() throws Exception{
//1 导入驱动类库
//2 注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//3 连接数据库
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day05", "root", "1234");
//4 操作数据库
Statement st = conn.createStatement();
String sql = " INSERT INTO `t_user` "+
" VALUES (NULL, 'tom', 18)" ;
st.executeUpdate(sql);
//5 关闭资源
st.close();
conn.close();
}
@Test
//发送查询语句
public void fun2() throws Exception{
//1 导入驱动类库
//2 注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//3 连接数据库
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day05", "root", "1234");
//4 操作数据库
Statement st = conn.createStatement();
String sql = " select * from t_user " ;
ResultSet rs = st.executeQuery(sql);
//遍历结果集中的内容并打印
while(rs.next()){
String name = rs.getString("name");
int id = rs.getInt("id");
int age = rs.getInt("age");
System.out.println(name+"==>"+age+"==>"+id);
}
//5 关闭资源
st.close();
conn.close();
}
}
关于DriverManager的细节问题
public class Demo {
@Test
public void fun1() throws Exception{
// 注册驱动
//注册方式1:不推荐 => 驱动实现类中 的静态代码以及调用过
// DriverManager.registerDriver(driver);
//注册方式2:推荐
Class.forName("com.mysql.jdbc.Driver");
}
@Test
public void fun2() throws Exception{
// 获得连接
DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/day05", "root", "1234");
//url完整 格式: 大协议:子协议://IP地址:端口号/库名?参数键=参数值
//完整: jdbc:mysql://127.0.0.1:3306/day05?useUnicode=true&characterEncoding=utf8
//简单: jdbc:mysql:///day05?useUnicode=true&characterEncoding=utf8
}
}
1> 注册驱动的问题.
DriverManager.registDriver(new Driver()); ==> 该种注册方式,在将来的开发中 不要使用.
使用如下方式:
Class.forName("com.mysql.jdbc.Driver");
2>为什么?
在驱动类的代码中,我们可以看到有一个静态代码块。 静态代码块中已经做了注册驱动的事情。 所以我们只需要加载
驱动类,就相当于调用了 registDriver 方法。
3>使用 Class.forName有什么好处?
* 如果调用registDriver 方法, 那么相当于创建了两个Driver对象,浪费资源.
* 使用forname的方式. 因为驱动类的名称是以字符串的形式填写,那么我们把该名称放到配置文件中,每次从配置文件中读取.
那么切换驱动类就非常方便. 也就意味着切换数据库方便.
关于Statement的细节问题
//Statement细节
public class Demo {
@Test
//execute 原始,增删改查都可以 返回值 true=> 查询有结果集 | false=> 查询没有结果集
//executeBatch 批量执行sql
//executeUpdate 执行增删改
//executeQuery 执行查询
public void fun1() throws Exception{
//1 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2 获得连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day05", "root", "1234");
//3 创建Statement
Statement st = conn.createStatement();
//4 书写sql
String sql = " INSERT INTO `t_user` "+
" VALUES (NULL, 'jerry', 16)" ;
//5 执行sql
boolean result = st.execute(sql);
System.out.println(result);//false
//6关闭资源
st.close();
conn.close();
}
@Test
public void fun2() throws Exception{
//1 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2 获得连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day05", "root", "1234");
//3 创建Statement
Statement st = conn.createStatement();
//4 书写sql
String sql = "select * from t_user" ;
//5 执行sql
boolean result = st.execute(sql);
if(result){
ResultSet rs = st.getResultSet();
System.out.println(rs);
}
//6关闭资源
st.close();
conn.close();
}
@Test
//executeUpdate
public void fun3() throws Exception{
//1 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2 获得连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day05", "root", "1234");
//3 创建Statement
Statement st = conn.createStatement();
//4 书写sql
String sql = " INSERT INTO `t_user` "+
" VALUES (NULL, 'jack', 20)" ;
//5 执行sql
int row = st.executeUpdate(sql);
if(row!=1){
throw new RuntimeException("插入失败!");
}
System.out.println(row);//1
//6关闭资源
st.close();
conn.close();
}
@Test
//executeQuery
public void fun4() throws Exception{
//1 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2 获得连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day05", "root", "1234");
//3 创建Statement
Statement st = conn.createStatement();
//4 书写sql
String sql = "select * from t_user" ;
//5 执行sql
ResultSet rs = st.executeQuery(sql);
//遍历rs
System.out.println(rs);
//6关闭资源
st.close();
conn.close();
}
}
6.Statement 对象
该对象可以理解为一个 向数据库运送sql语句的 "小车";
方法:
void addBatch(String sql) 向车上添加语句. (用于批量执行sql语句); insert update delete
int[] executeBatch() 将车上的语句 运送给数据库执行. 返回值存放每个语句执行后影响的行数. 因为是多个语句,所以用数组装.
void clearBatch() 清除车上的语句.
----以上3个方法是批量执行sql相关的(下午最后一节课演示)----------------------
boolean execute(String sql) 执行一个sql语句. 如果该语句返回结果集 返回值为true(select). 如果该语句不返回结果集 返回false(insert update delete);
ResultSet executeQuery(String sql) 执行一个有结果集的查询. 会将结果集包装到resultset对象中.(select)
int executeUpdate(String sql) 执行一个没有结果集的语句. 会将语句影响的行数返回.(insert update delete)
结论:
执行查询语句时使用: executeQuery方法
执行增删改等语句时使用: executeUpdate方法
关于ResultSet的细节
Demo 1
//ResultSet细节
//功能: 封装结果集数据
//操作: 如何获得(取出)结果
//结论:
//1. next方法,向下移动并判断是否有内容
//2. getXXX方法,根据列索引或列名获得列的内容
public class Demo {
@Test
public void fun1() throws Exception{
//1 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2 获得连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day05", "root", "1234");
//3 创建Statement
Statement st = conn.createStatement();
//4 书写sql
String sql = "select * from t_user" ;
//5 执行sql
ResultSet rs = st.executeQuery(sql);
//向下移动一行,并判断
while(rs.next()){
//有数据
//取数据:getXXX
int id = rs.getInt(1);//获得第一列的值
//int id rs.getInt("id");// 获得id列的值
String name = rs.getString(2);//获得第二列的值
int age = rs.getInt(3);//获得第三列的值
System.out.println(id+"==>"+name+"==>"+age);
}
//6关闭资源
st.close();
conn.close();
}
/* 数据库类型 java类型
int int
double double
decimal double
char String
varchar String
datetime Date
timestamp Timestamp/Date
*/
}
Demo 2
//ResultSet细节
// 1.结果集的滚动 => 移动结果集的指针就是滚动
// 2.结果集反向修改数据库
public class Demo2 {
@Test
public void fun1() throws Exception{
//1 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2 获得连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day05", "root", "1234");
//3 创建Statement
Statement st = conn.createStatement();
//4 书写sql
String sql = "select * from t_user" ;
//5 执行sql
ResultSet rs = st.executeQuery(sql);
//倒着遍历
//1> 光标移动到最后一行之后
rs.afterLast();
//2> 遍历=>
while(rs.previous()){//向上移动光标,并判断是否有数据
int id = rs.getInt("id");// 获得id列的值
String name = rs.getString("name");//获得第二列的值
int age = rs.getInt("age");//获得第三列的值
System.out.println(id+"==>"+name+"==>"+age);
}
//6关闭资源
st.close();
conn.close();
}
/* 数据库类型 java类型
int int
double double
decimal double
char String
varchar String
datetime Date
timestamp Timestamp/Date
*/
}
Demo 3
//ResultSet细节
// 2.结果集反向修改数据库
public class Demo3 {
@Test
public void fun1() throws Exception{
//1 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2 获得连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day05", "root", "1234");
//3 创建Statement
Statement st = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_UPDATABLE);
//4 书写sql
String sql = "select * from t_user" ;
//5 执行sql
ResultSet rs = st.executeQuery(sql);
//使用结果集 反向修改数据库
rs.next();//将光标移动到第一行
rs.updateString("name", "汤姆");// 修改第一行name列的值为中文汤姆
rs.updateRow();// 确认修改
//6关闭资源
st.close();
conn.close();
}
}
参数问题:
参数1 resultSetType - 结果集类型
ResultSet.TYPE_FORWARD_ONLY、 不支持结果集滚动,只能向前.
ResultSet.TYPE_SCROLL_INSENSITIVE 支持滚动, 迟钝,不敏感的结果集.
ResultSet.TYPE_SCROLL_SENSITIVE 支持滚动, 敏感的结果集.
参数2 resultSetConcurrency - 结果是否支持修改类型
ResultSet.CONCUR_READ_ONLY 不支持修改
ResultSet.CONCUR_UPDATABLE 支持修改
JDBC中关于释放资源的一些问题
9.释放资源
1> 从小到大释放. resultSet < Statement < Connection
2> 3个都需要释放.
3>释放时调用close方法即可. 如果其中一个对象的关闭 出现了异常. 也要保证其他的对象关闭方法被调用.
resultSet.close();
Statement.close();
Connection.close();
以上代码是无法保证一定都能执行的.
try{
resultSet.close();
}catch(Exception e){
}finally{
try{
Statement.close();
}catch(Exception e){
}
finally{
try{
Connection.close();
}catch(Exception e){
}
}
}
自己封装的一JDBC工具类
import java.io.FileInputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class JDBCUtils {
private static String driver;
private static String url;
private static String user;
private static String password;
static{
try {
//0读取配置文件
Properties prop = new Properties();
InputStream is = new FileInputStream("src/db.properties");
prop.load(is);
is.close();
driver = prop.getProperty("driver");
url = prop.getProperty("url");
user = prop.getProperty("user");
password = prop.getProperty("password");
//1 注册驱动
Class.forName(driver);
} catch (Exception e) {
e.printStackTrace();
}
}
//1 获得连接
public static Connection getConnection(){
Connection conn = null;
try {
//2 获得连接
conn = DriverManager.getConnection(url, user, password);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("创建连接失败!");
}
return conn;
}
//2 释放资源
//1> 参数可能为空
//2> 调用close方法要抛出异常,确保即使出现异常也能继续关闭
//3>关闭顺序,需要从小到大
public static void close(Connection conn , Statement st , ResultSet rs){
try {
if(rs!=null){
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
try {
if(st!=null){
st.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try {
if(conn!=null){
conn.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
System.out.println(getConnection());
}
}
SQL的注入问题
- 早年登录逻辑,就是把用户在表单中输入的用户名和密码 带入如下sql语句. 如果查询出结果,那么 认为登录成功.
SELECT * FROM USER WHERE NAME='' AND PASSWORD='xxx';
- 如果将用户名以及密码数据写成如下的形式:
SELECT * FROM USER WHERE NAME='xxx' OR 1=1 -- ' and password='xxx';
- 发现sql语句失去了判断效果,条件部分成为了恒等式.
- 导致网站可以被非法登录, 以上问题就是sql注入的问题.
那么我们如何解决SQL注入问题?
- 解决办法:在运送sql时,我们使用的是Statement对象. 如果换成prepareStatement对象,那么就不会出现该问题.
- sql语句不要再直接拼写.而要采用预编译的方式来做.
- 完成如上两步.即可解决问题.
*为什么使用PrepareStatement对象能解决问题?
sql的执行需要编译. 注入问题之所以出现,是因为用户填写 sql语句 参与了编译. 使用PrepareStatement对象
在执行sql语句时,会分为两步. 第一步将sql语句 "运送" 到mysql上编译. 再回到 java端 拿到参数 运送到mysql端.
用户填写的 sql语句,就不会参与编译. 只会当做参数来看. 避免了sql注入问题;
PrepareStatement 在执行 母句相同, 参数不同的 批量执行时. 因为只会编译一次.节省了大量编译时间.效率会高.
演示向mysql中存放大文本文件
public class Demo {
@Test
//演示向mysql中存放大文本数据
//存储大文本必须使用PrepareStatement对象
public void fun1() throws Exception{
//1 获得连接
Connection conn = JDBCUtils.getConnection();
//2 书写sql
String sql = "insert into mytext values(null,?)";
//3 创建PrepareStatement
PreparedStatement ps = conn.prepareStatement(sql);
//4 设置参数
//参数1:参数的索引
//参数2:需要保存的文本的流
//参数3:文件长度
File f = new File("src/text.txt");
FileReader reader = new FileReader(f);
ps.setCharacterStream(1, reader, (int)f.length());
//5 执行sql
int result = ps.executeUpdate();
System.out.println(result);
//6关闭资源
JDBCUtils.close(conn, ps, null);
}
}
通过JDBC存储二进制文件
public class Demo {
@Test
//演示向mysql中存放图片
//存储图片必须使用PrepareStatement对象
public void fun1() throws Exception{
//1 获得连接
Connection conn = JDBCUtils.getConnection();
//2 书写sql
String sql = "insert into myblob values(null,?)";
//3 创建PrepareStatement
PreparedStatement ps = conn.prepareStatement(sql);
//4 设置参数
//参数1:参数的索引
//参数2:需要保存的图片的流
//参数3:图片文件长度
File f = new File("src/wg.PNG");
InputStream is = new FileInputStream(f);
ps.setBinaryStream(1, is, (int)f.length());
//5 执行sql
int result = ps.executeUpdate();
System.out.println(result);
//6关闭资源
JDBCUtils.close(conn, ps, null);
}
}
批量执行MYSQL语句
public class Demo {
@Test
//1 使用Statement对象批量执行sql
public void fun1() throws Exception{
//1 获得连接
Connection conn = JDBCUtils.getConnection();
//2 获得Statement
Statement st = conn.createStatement();
//3 添加多条sql语句到st中
st.addBatch("create table t_stu ( id int primary key auto_increment , name varchar(20) )");
st.addBatch("insert into t_stu values(null,'tom')");
st.addBatch("insert into t_stu values(null,'jerry')");
st.addBatch("insert into t_stu values(null,'jack')");
st.addBatch("insert into t_stu values(null,'rose')");
//4 执行sql
int[] results = st.executeBatch();
System.out.println(Arrays.toString(results));
//5关闭资源
JDBCUtils.close(conn, st, null);
}
@Test
//2 使用PrepareStatement对象批量执行sql
public void fun2() throws Exception{
//1 获得连接
Connection conn = JDBCUtils.getConnection();
//2 书写sql语句
String sql = "insert into t_stu values(null,?)";
//3 创建PrepareStatement
PreparedStatement ps = conn.prepareStatement(sql);
//4 循环.添加参数
for(int i=0;i<100;i++){
ps.setString(1, "用户"+i);
ps.addBatch();
}
//5 批量执行
int[] results =ps.executeBatch();
System.out.println(Arrays.toString(results));
//5关闭资源
JDBCUtils.close(conn, ps, null);
}
}