1.Statement的操作数据库
1 加载驱动, 通过驱动连接数据库
static{
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
2 创建连接 选择数据库的类型mysql
输入连接名,ip port 3306 选择数据库 用户名 root 密码 自己设置的密码
// 创建连接
try {
Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/s_t", "root", "1234");
} catch (SQLException e) {
e.printStackTrace();
}
3 在当前连接下创建查询窗口
// 基于连接conn对象,创建查询窗口对象
Statement state = conn.createStatement();
4 写sql语句
String sql="insert into student(sno,sname,sage) values('0899903','abc',4)";
5 执行sql语句
// 执行语句的功能,在查询窗口上, 运行的方法在state中
int i = state.executeUpdate(sql);
6 查看运行结果
// 查看结果
System.out.println(i);
7 关闭查询窗口,关闭连接
finally {
// 防止关闭时出现空指针的报错,判断
try {
if (state!=null){
state.close();
}
if(conn!=null){
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
优化上面封装的方法,已添加为例:
添加方法缺点:
1 我们每次添加的数据,每次调用方法前都要进行修改
----------》数据灵活,调用方法传入什么值,变量就是什么值---》数据方法的参数
public static void addTest(String sno,String name,Integer sage){
1 sql语句的书写:不可行
向表中插入空数据,values(表中的列名),不会插入真正的数据
String sql="insert into student(sno,sname,sage) values(sno,sname,sage)";
2 sql语句的书写方式,sno认为是需要插入的字符串
String sql="insert into student(sno,sname,sage) values('sno','sname',sage)"
3 sql语句: 字符串的拼接 把变量直接放到sql语句,但是没有sql语句对于字符串进行标识的单引号
String sql="insert into student(sno,sname,sage) values("+sno+","+sname+","+sage+")";
4 成功的语句
String sql="insert into student(sno,sname,sage) values('"+sno+"','"+sname+"',"+sage+")";
2.PreparedStatement
public static void addEnd(String sql,Object... objs){
Connection conn=null;
PreparedStatement pstmt=null;
// 创建连接
try {
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/s_t", "root", "1234");
// 基于连接conn对象,创建查询窗口对象
// 语句:sql语句中固定的部分进行了书写
// 例如 向student表中数据的插入,但是语句中不固定是要插入的数据值
// 不固定的数据值,用英文状态下的问号占位,要插入几个值,就写介个问号=====》问号,占位
pstmt = conn.prepareStatement(sql);
// 运行语句前,要把sql中?占位的位置,进行赋值,sql语句完整
// pstmt.set+数据类型 例如 sno赋值,sno字符串类型,setString
// 给问号进行赋值,?位置从1开始
for(int i=0;i<objs.length;i++){
pstmt.setObject(i+1,objs[i] );
}
int i = pstmt.executeUpdate();
// 查看结果
System.out.println(i);
} catch (SQLException e) {
e.printStackTrace();
}finally {
// 防止关闭时出现空指针的报错,判断
try {
if (pstmt!=null){
pstmt.close();
}
if(conn!=null){
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
3.优化(增删改方法)
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/s_t", "root", "1234");
连接,修改不方便,===============》定义一次,多次使用========》变量
private static final String URL="jdbc:mysql://127.0.0.1:3306/s_t";
private static final String USER="root";
private static final String PWD="root";
public static Connection getConn(){
Connection conn=null;
try {
conn = DriverManager.getConnection(URL, USER, PWD);
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
public static int executeUpdate(String sql,Object... objs){
Connection conn=getConn();
PreparedStatement pstmt=null;
int result=0;
try {
pstmt = conn.prepareStatement(sql);
for(int i=0;i<objs.length;i++){
pstmt.setObject(i+1,objs[i] );
}
result = pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
try {
if (pstmt!=null){
pstmt.close();
}
if(conn!=null){
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return result;
}
3.1 java向数据库写数据,出现?
java向数据库写数据,出现?,不是编码集,从java向数据库传入时,
没有指定编码集,数据没有被识别
解决中文问题:
private static final String URL="jdbc:mysql://127.0.0.1:3306/s_t?useUnicode=true&characterEncoding=utf8";
3.2 sql注入
使用Statement容易出现sql注入,如下Statement需要自己拼接sql语句,如果后面拼接or 1=1 查询条件恒成立,会查出所有数据,会造成数据安全问题
public static void get1(String sno){
List<Student> lists = new ArrayList<>();
Connection conn = JdbcUtil.getConn();
Statement state=null;
ResultSet rs=null;
try {
state = conn.createStatement();
String sql = "select * from student where sno = '"+sno+"' or 1=1";
*************************
语句问题:
数据不安全,======》sql注入
***************************
rs = state.executeQuery(sql);
// 结果集的数据如何查看
while (rs.next()){
// 每次拿到的数据都是一条数据 一条数据由多个列组成
// 一条数据如果想要进行获取,需要获取到当前这一条数据每个列上的值
// rs.get+数据类型 例如:获取sno列,数据类型字符串,getString
String sno1 = rs.getString("sno");
String sname = rs.getString("sname");
int sage = rs.getInt("sage");
String ssex = rs.getString("ssex");
String dept = rs.getString("dept");
Student s = new Student();
s.setDept(dept);
s.setSage(sage);
s.setSname(sname);
s.setSno(sno1);
s.setSsex(ssex);
System.out.println(s);
lists.add(s);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
try{
if(conn!=null){
conn.close();
}
if(state!=null){
state.close();
}
if(rs!=null){
rs.close();
}
}catch (SQLException e){
e.printStackTrace();
}
}
}
prepareStatement不需要手动进行拼接,不需要关注参数注入,使用站位即可,可以减少注入的风险,降低数据安全风险
使用preparedStatement
public static void getPre(String sno){
List<Student> lists = new ArrayList<>();
Connection conn = JdbcUtil.getConn();
PreparedStatement pstmt=null;
ResultSet rs=null;
try {
String sql = "select * from student where sno =?";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1,sno);
*************************
自己不用关注参数注入,使用方法进行的占位
=============》如果非要加后面的条件,出现注入
比较:
相比较而言:PreparedStatement 安全,在网络上,没有绝对的安全
*********************
rs = pstmt.executeQuery();
// 结果集的数据如何查看
while (rs.next()){
// 每次拿到的数据都是一条数据 一条数据由多个列组成
// 一条数据如果想要进行获取,需要获取到当前这一条数据每个列上的值
// rs.get+数据类型 例如:获取sno列,数据类型字符串,getString
String sno1 = rs.getString("sno");
String sname = rs.getString("sname");
int sage = rs.getInt("sage");
String ssex = rs.getString("ssex");
String dept = rs.getString("dept");
Student s = new Student();
s.setDept(dept);
s.setSage(sage);
s.setSname(sname);
s.setSno(sno1);
s.setSsex(ssex);
System.out.println(s);
lists.add(s);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
try{
if(conn!=null){
conn.close();
}
if(pstmt!=null){
pstmt.close();
}
if(rs!=null){
rs.close();
}
}catch (SQLException e){
e.printStackTrace();
}
}
}
4. prepareStatement操作数据库最终封装
1.需要定义一个接口,接口的目的是,我传给你一个ResultSet对象,你讲ResultSet对象的值,赋值给,一个实体对象,并且将该实体对象返回
import java.sql.ResultSet;
public interface RowMap<T> {
/**
* 传入ResultSet对象,返回一个对象
* @param res
* @return
*/
T rowMapping(ResultSet res);
}
2.封装的增删改查的方法类
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class JdbcUtil {
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
static String URL = "jdbc:mysql://127.0.0.1:3306/s_t?useUnicode=true&characterEncoding=UTF-8";
static String NAME = "root";
static String PASSWORD = "123456";
private static Connection conn = null;
/**
* 获取Connection连接
* @return
*/
public static Connection getConnection() {
try {
conn = DriverManager.getConnection(URL, NAME, PASSWORD);
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
/**
* 将sql的参数注入到sql中,返回PreparedStatement对象
* @param sql
* @param param
* @return
*/
public static PreparedStatement setJdbcData(String sql, Object[] param){
Connection conn = getConnection();
PreparedStatement pst = null;
try {
pst = conn.prepareStatement(sql);
for (int i = 0; i < param.length; i++) {
pst.setObject(i + 1, param[i]);
}
} catch (SQLException e) {
e.printStackTrace();
}
return pst;
}
/**
* 增删改
* @param sql sql
* @param param 参数
* @return
*/
public static int update(String sql, Object... param) {
int result = 0;
PreparedStatement pst = setJdbcData(sql, param);
try {
result = pst.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
close(conn, pst);
}
return result;
}
/**
* 查询sql信息,并且返回一个List对象集合
* @param sql
* @param rowMap 接口,目的:将result的信息赋值给实体对象,并且返回该实体对象
* @param param sql中填写站位的参数
* @param <T> 实体对象
* @return
*/
public static <T> List<T> query(String sql, RowMap<T> rowMap, Object... param){
PreparedStatement pst = setJdbcData(sql, param);
ArrayList<T> list = new ArrayList<>();
ResultSet res = null;
try {
res = pst.executeQuery();
while (res.next()){
/**
* 我们查询的数据,应该用对象存储,我们数据库一张表就是对应java一个实体类,因此我new一个实体类对象,将一条数据存在一个实体类对象中。
* 我们现在有个问题,每张表对应不同的实体类,我们传进来的sql不同,就是对应不同的表,因此对应不同的对象;
* rowMap.rowMapping的方法就是传入你一个result,将result的信息赋值给对象,并且返回该对象
*/
T t = rowMap.rowMapping(res);
list.add(t);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
close(conn, pst, res);
}
return list;
}
/**
* 关闭方法
* @param conn
* @param pst
*/
public static void close(Connection conn, PreparedStatement pst) {
try {
if (pst != null) {
pst.close();
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 关闭方法
* @param conn
* @param pst
* @param res
*/
public static void close(Connection conn, PreparedStatement pst, ResultSet res) {
try {
if (res != null) {
res.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
close(conn, pst);
}
}
3.测试封装的方法
3.1 创建实体类(我们一张表就是一个实体类)目的是将查询的数据放到实体类中
public class Student {
private String Sno;
private String Sname;
private String Ssex;
private int Sage;
private String Dept;
public Student(String sno, String sname, String ssex, int sage, String dept) {
Sno = sno;
Sname = sname;
Ssex = ssex;
Sage = sage;
Dept = dept;
}
public Student() {
}
public String getSno() {
return Sno;
}
public void setSno(String sno) {
Sno = sno;
}
public String getSname() {
return Sname;
}
public void setSname(String sname) {
Sname = sname;
}
public String getSsex() {
return Ssex;
}
public void setSsex(String ssex) {
Ssex = ssex;
}
public int getSage() {
return Sage;
}
public void setSage(int sage) {
Sage = sage;
}
public String getDept() {
return Dept;
}
public void setDept(String dept) {
Dept = dept;
}
@Override
public String toString() {
return "Student{" +
"Sno='" + Sno + '\'' +
", Sname='" + Sname + '\'' +
", Ssex='" + Ssex + '\'' +
", Sage=" + Sage +
", Dept='" + Dept + '\'' +
'}';
}
}
3.2 测试方法
import com.zxb.test16.mapper.Student;
import java.sql.SQLException;
import java.util.List;
public class test {
public static void main(String[] args) {
/**
* 查询数据
*/
String sql1 = "SELECT * FROM student WHERE dept in (?,?) GROUP BY dept";
List<Student> list = JdbcUtil.query(sql1,(res)->{
Student stu = new Student();
try {
stu.setSno(res.getString("Sno"));
stu.setSname(res.getString("Sname"));
stu.setSsex(res.getString("Ssex"));
stu.setSage(res.getInt("Sage"));
} catch (SQLException e) {
e.printStackTrace();
}
return stu;
}, "计算机系", "信息管理系");
System.out.println(list);
/**
* 测试修改,返回影响行数
*/
String sql2 = "update student set Sage=108 WHERE Sno=?";
int num = JdbcUtil.update(sql2, "0831104");
System.out.println(num);
}
}
5. 比较查询statement、prepareStatement对象,查询操作数据库效率
从下面实验中可以看出statement插入2000条数据,需要1729ms,而preparedStatement插入2000条数据,需要1580ms.可以看的出,preparedStatement效率较高与statement
import com.zxb.util.JdbcUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
public class demo01 {
public static void main(String[] args) {
/**
* 测试statement
*/
// long start = System.currentTimeMillis();
// statementTest();
// System.out.println("statement总共耗费时间是:" +(System.currentTimeMillis() - start)); //statement总共耗费时间是:1729
/**
* 测试
*/
long start = System.currentTimeMillis();
preparedStatementTest();
System.out.println("preparedStatement总共耗费时间是:" +(System.currentTimeMillis() - start)); //preparedStatement总共耗费时间是:1580
}
public static void statementTest() {
/**
* 使用statement,插入2000条数据
*/
Statement statement = null;
Connection conn = JdbcUtil.getConnection();
try {
statement = conn.createStatement();
for (int i = 0; i < 2000; i++) {
String sql = "insert into test(name,age) values('" + "张三" + i + "'," + i + ")";
//批量创建,把所有创建的sql语句存到statement对象
statement.addBatch(sql);
}
//批量执行有存在statement对象中的sql语句
statement.executeBatch();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
if (statement != null) {
statement.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void preparedStatementTest(){
/**
* 使用prepareStatement,插入2000条数据
*/
Connection conn = JdbcUtil.getConnection();
String sql = "insert into test(name,age) values(?,?)";
PreparedStatement pst = null;
try {
pst = conn.prepareStatement(sql);
for (int i = 0; i < 2000; i++) {
pst.setString(1, "张三"+i);
pst.setInt(2, i);
//批量存储执行语言
pst.addBatch();
}
//批量执行
pst.executeBatch();
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtil.close(conn, pst);
}
}
}
Statement 和 PreparedStatement比较:
两者关系:
PreparedStatement 是Statement的子接口
子接口拥有父接口的方法,可以进行方法的扩展
区别:
1 数据的增删改,进行参数注入,Statement需要拼接,语句麻烦
PreparedStatement 通过方法进行的参数注入,注入方式简单
2 数据查询:
Statement sql 注入情况
PreparedStatement 预防sql注入
3 效率问题:
Statement 普通的查询窗口对象
PreparedStatement 预处理的查询查询窗口
Statement 普通的查询窗口对象
state = conn.createStatement();
String sql = "select * from student where sno = '"+sno+"' or 1=1";
rs = state.executeQuery(sql);
运行sql语句是,进行sql语句的传入==========》编译
PreparedStatement 预处理的查询查询窗口
String sql = "select * from student where sno =?";
pstmt = conn.prepareStatement(sql);========》提前编译(提前准备)
pstmt.setString(1,sno);
rs = pstmt.executeQuery();======》运行时不用在进行语句的编译,因此PreparedStatement运行效率快!
PreparedStatement 与 statement
1. PreparedStatement 可以减少sql注入的安全问题;
2. PreparedStatement 执行效率也高于statement,由于PreparedStatement先会将sql传到conn中,进行预处理提前进行编译,而statement会在sql语句传入时候进行编译;
6. 事务操作
jdbc中,自动提交默认开启
查看事务:
自动提交关闭: 默认是true 默认开启
在jdbc中设置了自动提交关闭,就会默认开启了一个事务
conn.setAutoCommit(false);
conn.commit(); 提交事务
1.读未提交
1.1 我们打开一个mysql客户端,设置事务的隔离级别
set tx_isolation="read-uncommitted"
1.2 查询数据,现在目前最新的数据
SELECT * from student WHERE Sno= '0831104';
2. java代码在String a = null处,打断点,debug代码
import com.zxb.util.JdbcUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class demo02 {
/**
* JDBC操作事务
*/
public static void main(String[] args) {
Connection conn = JdbcUtil.getConnection();
String sql = "update student set Sage=? where Sno = ?";
try {
/**设置事务隔离级别
* 读未提交 int TRANSACTION_READ_UNCOMMITTED = 1;
* 读已提交 int TRANSACTION_READ_COMMITTED = 2;
* 不可重复读 int TRANSACTION_REPEATABLE_READ = 4;
* 串行化 int TRANSACTION_SERIALIZABLE = 8;
*/
conn.setTransactionIsolation(1);
//关闭自动提交,自动开启事务
conn.setAutoCommit(false);
PreparedStatement pst = conn.prepareStatement(sql);
pst.setInt(1, 20);
pst.setString(2, "0831104");
pst.executeUpdate();
/**
* 构造代码出现空指针异常
*/
String a = null;
String s = a.toUpperCase();
conn.commit(); // 只要事务执行过程中不发生异常,执行手动提交
} catch (Exception e) {
e.printStackTrace();
try {
//代码发生异常 事务回滚
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
}
3.此时mysql客户继续执行查询语句,查询结果显示脏读
SELECT * from student WHERE Sno= '0831104';
2.读已提交
2.1 我们打开一个mysql客户端,设置事务的隔离级别
set tx_isolation="read-committed";
2.2. java代码在conn.commit()处,打断点,debug代码
import com.zxb.util.JdbcUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class demo02 {
/**
* JDBC操作事务
*/
public static void main(String[] args) {
Connection conn = JdbcUtil.getConnection();
String sql = "update student set Sage=? where Sno = ?";
try {
/**设置事务隔离级别
* 读未提交 int TRANSACTION_READ_UNCOMMITTED = 1;
* 读已提交 int TRANSACTION_READ_COMMITTED = 2;
* 不可重复读 int TRANSACTION_REPEATABLE_READ = 4;
* 串行化 int TRANSACTION_SERIALIZABLE = 8;
*/
conn.setTransactionIsolation(2);
//关闭自动提交,自动开启事务
conn.setAutoCommit(false);
PreparedStatement pst = conn.prepareStatement(sql);
pst.setInt(1, 20);
pst.setString(2, "0831104");
pst.executeUpdate();
conn.commit(); // 只要事务执行过程中不发生异常,执行手动提交
} catch (Exception e) {
e.printStackTrace();
try {
//代码发生异常 事务回滚
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
2.3 此时我们查询该数据,显示还是未修改前的数据
2.4 java代码继续debug,执行commit代码后,再次查询该数据,显示已更新的数据
3.不可重复读、串行化也是如此操作,只需要修改conn.setTransactionIsolation()
//不可重复读
conn.setTransactionIsolation(4)
//串行化
conn.setTransactionIsolation(8)