JDBC学习笔记
JDBC概述
- 刚开始通过客户端连接数据库
- 因为数据库厂商有Mysql,Oracle,DB2,SqlServer等等,所以SUN公司提出了连接数据库的规范
JDBC:Java DataBase Connectivity(java数据库连接)
连接MySQL数据库
MySQL数据库这个厂商需要提供对JDBC接口的实现类,这些实现类被打包成了一个Jar,如果Java项目要使用,必须要跟项目建立依赖关系。
Statement 是 Java 执行数据库操作的一个重要接口,用于在已经建立数据库连接的基础上,向数据库发送要执行的SQL语句。Statement对象,用于执行不带参数的简单SQL语句。
1.java.sql包下常用的接口
public interface Driver
:每个数据库厂商必须实现该驱动接口public class DriverManager
:管理驱动类public interface Connection
:建立数据库连接public interface Statement
:执行SQL语句public interface ResultSet
:表示数据库结果集的数据表,将查询后的虚拟表封装成Java对象ResultSet
2.JDBC连接数据库的步骤
示例1:变更语句:executeUpdate
准备:导入驱动包并建立依赖
package com.yu;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.Statement;
import java.util.Properties;
public class A_变更语句 {
public static void main(String[] args) throws Exception {
//1.加载驱动类——面向接口编程
/*
* driver在编译期间只可使用java.sql.Driver()接口定义的变量与方法
* driver在运行期间使用java.sql.Driver()接口实现的方法
* */
Driver driver = new com.mysql.jdbc.Driver();//接口回调,编译时异常处理
//2.建立数据库连接通道
/*
* URL:数据库访问路径
* Properties:提供用户名和密码
* */
//String url="http://www.baidu.com:80"=>协议://IP地址:“端口号”;
//?verifyServerCertificate=false&useSSL=false:禁用SSL characterEncoding=UTF-8:防止数据库语言乱码
String url = "jdbc:mysql://127.0.0.1:3306/yue?verifyServerCertificate=false&useSSL=false&characterEncoding=UTF-8";
Properties props = new Properties();
props.setProperty("user","root");
props.setProperty("password","");
Connection conn = driver.connect(url,props);
//System.out.println("coon = " + conn);//=>控制台输出地址:coon = com.mysql.jdbc.JDBC4Connection@326de728
//3.设置执行的sql指令
String sql = "INSERT INTO student(student_name,student_sex,age,birthday) values ('老王','女',66,null)";
//4.获取执行sql语句对象
Statement statement = conn.createStatement();
//5.执行sql变更语句:execute
statement.executeUpdate(sql);
//6.关闭资源(顺序由内向外)
statement.close();
conn.close();
}
}
结果:
示例2:查询语句:executeQuery
package com.yu;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
public class B_查询语句 {
public static void main(String[] args) throws Exception {
//1.加载驱动类——面向接口编程
Driver driver = new com.mysql.jdbc.Driver();//接口回调,编译时异常处理
//2.建立数据库连接通道
String url = "jdbc:mysql://127.0.0.1:3306/yue?verifyServerCertificate=false&useSSL=false&characterEncoding=UTF-8";
Properties props = new Properties();
props.setProperty("user","root");
props.setProperty("password","");
Connection conn = driver.connect(url,props);
//3.设置执行的sql指令
String sql = "select * from student";
//4.获取执行sql语句对象
Statement statement = conn.createStatement();
//5.执行sql变更语句:execute ResultSet是将虚拟表封装了
ResultSet resultSet = statement.executeQuery(sql);
System.out.println("resultSet = " + resultSet);
//6.关闭资源(顺序由内向外)
resultSet.close();
statement.close();
conn.close();
}
}
3.JDBC优化
准备
如果我们没有提供数据库厂商的Jar,那么我们写上述两个类在编译期间就无法通过
Driver driver = new com.mysql.jdbc.Driver();//com.ysql.jdbc.Driver必须存在
用class.forName()方法不会执行com.mysql.jdbc.Driver
类的构造方法,会直接直接静态代码块中的DriverManager.registerDriver(new Driver());
创建驱动类的对象而不需要com.mysql.jdbc.Driver(是字符串)该类存在,编译便可通过。解释如下:
com.mysql.jdbc.Driver
类做了什么?
调用了构造方法
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {//处理编译时异常
//构造方法,创建对象使用和对实例变量的初始化
}
static {//静态代码块随着类的加载而加载,只是加载一次,如果使用Class.forName加载com.mysql.jdbc.Driver(是字符串)
try {
DriverManager.registerDriver(new Driver());//new Driver()创建驱动类的对象
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
如果使用Class.forName加载com.mysql.jdbc.Driver(因为这个位置是字符串,可以随意改,不需要该类真实存在,编译不会报错,顶多运行时报错)会执行静态代码块,而不会加载构造方法
编译通过,该类在运行的时候才报错ClassNotFoundException
package com.os.test;
public class C_静态代码块 {
public static void main(String[] args)throws Exception {
//随着类的加载而加载,加载类到“方法区”(类的信息、全局数据区域static、常量池)
//Class clazz1 = Person.class;//不会执行静态代码块
//System.out.println(clazz1);
//创建对象的时候才开始加载
//Person p1 = (Person)clazz1.newInstance();
//System.out.println("p1 = " + p1);
//Person p2 = new Person();//先加载类的信息,才能创建对象,会执行静态代码块
/*
* 上述两种方式,我们使用的类必须存在,如果不存在,编译无法通过
* */
//手动加载类
//Class.forName("com.osasdfasdf.asdfsadf.asdf.sadf");//编译通过,该类在运行的时候才报错ClassNotFoundException
Class.forName("com.os.test.Person");//加载静态代码块
}
}
class Person{
static{
System.out.println("静态代码块:随着类的加载而加载");
}
public Person(){
System.out.println("Person的构造方法");
}
}
静态代码块随着类的加载而加载,加载类到“方法区”(类的信息、全局数据区域static、常量池),所以;
Class clazz1 = Person.class=>不会执行静态代码块,这行代码只是获取了类对象,并没有加载类
Person p2 = new Person()=>可以执行静态代码块,因为代码创建了对象
Class.forName(“com.os.test.Person”)=>没有创建对象却加载静态代码块
Class.forName()的作用是要求JVM查找并加载指定的类,也就是说JVM会执行该类的静态代码段。**
与new区别:
1.Class.forName()返回的是一个类,new返回对象
2.new就相当于Class.forName(“XXX”).newInstance();
使用Class.forName()驱动类
package com.yu;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Properties;
public class B_改进代码 {
public static void main(String[] args) throws Exception {
//1.加载驱动类——面向接口编程
//Driver driver = new com.mysql.jdbc.Driver();//接口回调,编译时异常处理
Class.forName("com.mysql.jdbc.Driver");//底层已将帮我们创建了com.mysql.jdbc.Driver()对象,并且将该驱动对象使用DriverManage管理
//2.建立数据库连接通道
String url = "jdbc:mysql://127.0.0.1:3306/yue?verifyServerCertificate=false&useSSL=false&characterEncoding=UTF-8";
Connection conn = DriverManager.getConnection(url,"root","");
// System.out.println("coon = " + conn);//=>控制台输出地址:coon = com.mysql.jdbc.JDBC4Connection@326de728
//3.设置执行的sql指令
String sql = "INSERT INTO student(student_name,student_sex,age,birthday) values ('lao ','女',66,null)";
//4.获取执行sql语句对象
Statement statement = conn.createStatement();
//5.执行sql变更语句:execute
int row = statement.executeUpdate(sql);//此处是影响的行数
System.out.println("影响的行数 = " + row);
//6.关闭资源(顺序由内向外)
statement.close();
conn.close();
}
}
如果上述的代码,没有了源文件,我们就无法对访问路径等信息进行维护
所以设置配置文件
改进代码:
1.配置文件:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/yue?verifyServerCertificate=false&useSSL=false&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=
2.java代码:
package com.yu;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Properties;
public class C_属性文件 {
private static String DRIVER;
private static String URL;
private static String USERNAME;
private static String PWD;
static {
try {
Properties properties = new Properties();
//读取属性文件
InputStream in = C_属性文件.class.getClassLoader().getResourceAsStream("jdbc.properties");
//加载属性文件
properties.load(in);
//赋值
DRIVER = properties.getProperty("jdbc.driverClassName");
URL = properties.getProperty("jdbc.url");
USERNAME = properties.getProperty("jdbc.username");
PWD = properties.getProperty("jdbc.password");
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("加载配置文件失败!");
}
}
public static void main(String[] args) throws Exception {
//1.加载驱动类——面向接口编程
Class.forName(DRIVER);
//2.建立数据库连接通道
Connection conn = DriverManager.getConnection(URL,USERNAME,PWD);
// System.out.println("coon = " + conn);//=>控制台输出地址:coon = com.mysql.jdbc.JDBC4Connection@326de728
//3.设置执行的sql指令
String sql = "INSERT INTO student(student_name,student_sex,age,birthday) values ('瑶 ','女',66,null)";
//4.获取执行sql语句对象
Statement statement = conn.createStatement();
//5.执行sql变更语句:execute
statement.executeUpdate(sql);
//6.关闭资源(顺序由内向外)
statement.close();
conn.close();
}
}
结果集
一.查询:ResultSet
1.遍历数据
当执行SELECT语句之后,数据库中获取了“虚拟表(数据表)”(包含数据和虚拟表的字段信息),JDBC将虚拟封装成一个ResultSet对象返回给程序员使用。
ResultSet对象具有**指向当前数据行的光标
。
光标最初的所在位置,
被安置于第一行之前
,使用next方法可以将光标移动下一行,并且由于在ResultSet对象中没有更多行时返回false。
查询的结果集ResultSet的使用步骤:
- 光标(游标)向下移动一行
- 判断是否有数据
- 获取数据
(全文引入的工具类)代码:
DBHelper类:package com.yu;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
/**
* 工具类:获取数据库连接和关闭资源
* */
public class DBHelper {
private static String DRIVER;
private static String URL;
private static String USERNAME;
private static String PWD;
static {
try {
Properties properties = new Properties();
//读取属性文件
InputStream in = DBHelper.class.getClassLoader().getResourceAsStream("jdbc.properties");
//加载属性文件
properties.load(in);
//赋值
DRIVER = properties.getProperty("jdbc.driverClassName");
URL = properties.getProperty("jdbc.url");
USERNAME = properties.getProperty("jdbc.username");
PWD = properties.getProperty("jdbc.password");
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("加载配置文件失败!");
}
}
static {
try {
Class.forName(DRIVER);
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException("加载驱动类失败!");
}
}
public Connection getConnection(){
try {
return DriverManager.getConnection(URL,USERNAME,PWD);
} catch (SQLException throwables) {
throwables.printStackTrace();
throw new RuntimeException("建立数据库连接通道失败!");
}
}
public void close(Connection conn, Statement st,PreparedStatement ps, ResultSet rs){
if (rs!=null){
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (st!=null){
try {
st.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (ps!=null){
try {
ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (rs!=null){
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
public void close(Connection conn, Statement st){
this.close(conn,st,null,null);
}
public void close(Connection conn, Statement st,ResultSet rs){
this.close(conn,st,null,null);
}
public void close(Connection conn, PreparedStatement ps){
this.close(conn,null,ps,null);
}
public void close(Connection conn, PreparedStatement ps,ResultSet rs){
this.close(conn,null,ps,null);
}
}
java代码:
package com.Demo;
import com.yue.DBHelper;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class A_ResultSet获取数据 {
public static void main(String[] args) throws SQLException {
DBHelper dbHelper = new DBHelper();
//获取数据库连接
Connection conn = dbHelper.getConnection();
//设置指令
String sql = "SELECT student_id 学生编号,student_name 姓名,student_sex 性别,age 年龄,birthday 生日 FROM student";
//创建执行sql语句对象
Statement st = conn.createStatement();
//封装虚拟表,获取结果集对象
ResultSet rs = st.executeQuery(sql);//光标处于虚拟表第一行,也就是字段信息的前面,所以需要下移一行才能获得数据
while (rs.next()){//rs.next():返回布尔类型
System.out.print(rs.getInt("学生编号")+"\t");//索引值从1开始,和java不同
System.out.print(rs.getString("姓名")+"\t");
System.out.print(rs.getString("性别")+"\t"+"\t");
System.out.print(rs.getString("年龄")+"\t"+"\t"+"\t");
System.out.println(rs.getString("生日")+"\t");
};
//如果你Java中使用的是java.util.Date类型,我们ResultSet得到的日期类型为java.sql.Date,转换会丢失
// 使用(java.util.Date)rs.getObject("字段名")方法强转就不会丢失
//关闭资源
dbHelper.close(conn,st,rs);
}
}
结果:
2.获取记录数(两种方法)
一.光标移动:rs.beforeFirst()
public class B_获取记录数 {
public static void main(String[] args) throws SQLException {
DBHelper dbHelper = new DBHelper();
//获取数据库连接
Connection conn = dbHelper.getConnection();
//设置指令
String sql = "SELECT student_id studentId,student_code studentCode,student_name studentName ,student_age,sex FROM student";
//获取执行SQL语句对象
Statement st = conn.createStatement();
//获取结果集对象(封装虚拟表)
ResultSet rs = st.executeQuery(sql);//光标处于第一行的前面
rs.last();//移动到最后一行★★★★★
int rowCount = rs.getRow();//★★★★★
System.out.println("rowCount = " + rowCount);
System.out.println("当前关闭已经处于最后一行,无法在进行遍历了,将光标移动到第一行的前面");
rs.beforeFirst();//★★★★★移动至第一行前,即可继续遍历
while(rs.next()){//移动光标了,false代表没有记录了!
System.out.print(rs.getInt("studentId")+"\t");//rs.getXXX(虚拟表中的字段名获取数据)
System.out.print(rs.getString("studentCode")+"\t");
System.out.print(rs.getString("studentName")+"\t");
System.out.print(rs.getDouble("student_age")+"\t");
System.out.println(rs.getObject("sex"));
//如果你Java中使用的是java.util.Date类型,我们ResultSet得到的日期类型为java.sql.Date,转换会丢失
// (java.util.Date)rs.getObject("字段名")
}
}
二.普通方法
System.out.println("获取记录数:推荐使用,聚合函数");
sql = "SELECT COUNT(*) FROM student";
rs = st.executeQuery(sql);
rs.next();//★★★★★
rowCount = rs.getInt(1);//★★★★★
System.out.println("rowCount = " + rowCount);
//关闭资源
dbHelper.close(conn,st,rs);
3.获取列信息
ResultSet其实是获取的是行信息,当我们不知道你查询的有哪些列时候,如何遍历我们的数据。
ResultSetMetaData
是描述结果集的元数据对象,获得每个字段的类型、是否可以为空值等信息,有可以直接获取数据和列的个数。
适用情况:不知道列数,列名
public class C_获取列信息 {
public static void main(String[] args) throws SQLException {
DBHelper dbHelper = new DBHelper();
//获取数据库连接
Connection conn = dbHelper.getConnection();
//设置指令
String sql = "SELECT student_id studentId,student_code studentCode,student_name studentName ,student_age,sex FROM student";
//获取执行SQL语句对象
Statement st = conn.createStatement();
//获取结果集对象(封装虚拟表)
ResultSet rs = st.executeQuery(sql);//光标处于第一行的前面
rs.next();
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
System.out.println("获取列的总数量:"+columnCount);
//遍历获取对应的列名和是否为空,注意:mysql列的索引从1开始
for(int i=1;i<=columnCount;i++){
System.out.println("真实字段名:"+rsmd.getColumnName(i)+",字段的别名:"+rsmd.getColumnLabel(i)+",判断是否为空:"+rsmd.isNullable(i));
}
//获取每列中的数据,如果完成
rs.previous();//后退,移动到上一行
while (rs.next()){
for(int i=1;i<=columnCount;i++) {
String columnName = rsmd.getColumnLabel(i);//获取虚拟表中的字段名
System.out.print("字段名:"+columnName+",值:"+rs.getString(columnName)+"\t");
}
System.out.println();
}
//关闭资源
dbHelper.close(conn,st,rs);
}
}
结果:
4.ResultSet相关问题
当我们ResultSet查询结果集必须要及时的关闭资源,当关闭之后我们就无法再使用ResultSet对象了(内存地址依旧存在,但是无法操作)
//关闭资源
dbHelper.close(conn,st,rs);
System.out.println("rs = " + rs);
rs.first();
//控制台输出异常:Exception in thread "main" java.sql.SQLException: Operation not allowed after ResultSet closed
ResultSet二次封装
当关闭资源后便无法使用ResultSet对象
所以对ResultSet对象进行二次封装,这样java程序对其操作就不需要关心内部有没有关闭资源
1.ResultSet对象封装一条记录:Map<>
package com.Demo;
import com.yue.DBHelper;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
public class A_ResultSet二次封装一条数据 {
public static void main(String[] args) throws SQLException {
DBHelper dbHelper = new DBHelper();
//获取数据库连接
Connection conn = dbHelper.getConnection();
//设置指令
String sql = "SELECT student_id 学生编号,student_name 姓名,student_sex 性别,age 年龄,birthday 生日 FROM student where student_id = 6";
//创建执行sql语句对象
Statement st = conn.createStatement();
//封装虚拟表,获取结果集对象
ResultSet rs = st.executeQuery(sql);
//创建Map集合储存一条记录
Map<String,Object> resultMap = null;
while (rs.next()){
resultMap = new HashMap<>();
//对结果集进行二次封装
resultMap.put("学生编号",rs.getInt("学生编号")+"\t");
resultMap.put("姓名",rs.getString("姓名")+"\t");
resultMap.put("性别",rs.getString("性别")+"\t");
resultMap.put("年龄",rs.getDouble("年龄")+"\t");
resultMap.put("生日",rs.getDate("生日")+"\t");
};
//关闭资源
dbHelper.close(conn,st,rs);
//关闭资源后的操作
System.out.println(resultMap);//结果:null或一条记录
}
}
结果:
改良代码:
如果字段过多的,可根据我们查询的列进行自动的处理
package com.Demo;
import com.yue.DBHelper;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
public class A_ResultSet获取数据 {
public static void main(String[] args) throws SQLException {
DBHelper dbHelper = new DBHelper();
//获取数据库连接
Connection conn = dbHelper.getConnection();
//设置指令
String sql = "SELECT * FROM student where student_id = 6";
//创建执行sql语句对象
Statement st = conn.createStatement();
//封装虚拟表,获取结果集对象
ResultSet rs = st.executeQuery(sql);
//创建Map集合储存一条记录
Map<String,Object> resultMap = null;
while (rs.next()){
resultMap = new HashMap<>();
//对结果集进行二次封装
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
for (int i = 1; i < columnCount; i++) {
String columnName = rsmd.getColumnName(i);
Object columnValue = rs.getObject(columnName);//rs.getObject(i)也可
resultMap.put(columnName,columnValue);
}
};
//关闭资源
dbHelper.close(conn,st,rs);
//关闭资源后的操作
System.out.println(resultMap);//结果:null或一条记录
}
}
2.ResultSet对象封装多条记录:List
也是一条一条的处理,目前我们都是使用Java提供的容器完成
package com.Demo;
import com.yue.DBHelper;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class A_ResultSet获取数据 {
public static void main(String[] args) throws SQLException {
DBHelper dbHelper = new DBHelper();
//获取数据库连接
Connection conn = dbHelper.getConnection();
//设置指令
String sql = "SELECT * FROM student where student_id = 6";
//创建执行sql语句对象
Statement st = conn.createStatement();
//获取结果集对象(封装虚拟表)
ResultSet rs = st.executeQuery(sql);//光标处于第一行的前面
List<Map<String,Object>> resultList = null;
//判断是否由数据
if(rs.next()){
//1.对集合进行实例化
resultList = new ArrayList<>();
//2.获取列的元信息
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
//3.直接使用do...while循环
do{
//每次处理完一条记录
Map<String,Object> recordMap = new HashMap<>();
for(int i=1;i<=columnCount;i++){
String columnName = rsmd.getColumnLabel(i);
Object columnValue = rs.getObject(i);
recordMap.put(columnName,columnValue);
}
//就往集合中进行存储
resultList.add(recordMap);
}while(rs.next());
}
//关闭资源
dbHelper.close(conn,st,rs);
//关闭之后的操作
for (Map<String, Object> map : resultList) {
System.out.println(map);
}
}
}
预处理
1.数据进行处理
在JDBC中对执行SQL语句的对象提供了两种方式:Statement
和PrepareStatement
对象
在开发中推荐使用
PrepareStatement
对象, 原因如下:
使用Statement对象的代码测试:
package com.Demo02;
import com.yue.DBHelper;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class A_Statement对象 {
public static void main(String[] args) throws SQLException {
DBHelper dbHelper = new DBHelper();
Connection conn = dbHelper.getConnection();
String student_id = "1001";
String studnet_name = "陆'峰";//改为"陆峰"则无问题
String student_sex = "男";
Integer age = 19;
String sql = "INSERT INTO student (student_id,student_name,student_sex,age) VALUES ('"+student_id+"','"+studnet_name+"','"+student_sex+"',"+age+")";
Statement st = conn.createStatement();
st.executeUpdate(sql);//报错:check the manual that corresponds to your MySQL server version for the right syntax to use near '峰','男',19)' at line 1
}
}
原因:
String studentName = "陆‘’峰";
//假设控制台输入的字符串为"陆‘’峰"
控制台报错:
sql = INSERT INTO student (student_code,student_name,student_age,sex) VALUES('1007','陆‘’峰',19,'男')
"陆'峰"
中的“‘”
恰好同sql指令中前面的单引号配对,所以sql语句无法继续执行下去
解决方式:
如果是程序员自己,那么我就需要对特殊的字符必须进行单独的转义处理
String studentName = "陆\\'峰";//假设控制台输入的字符串为陆'峰,需要自己对该数据进行处理 陆\\'峰
可以使用第三方提供的jar工具包进行处理,但是不推荐
可以直接使用预处理方式,可以帮我们对数据进行自动的处理
使用PreparStatement对象的代码测试:
package com.Demo02;
import com.yue.DBHelper;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
public class B_PreparStatement预处理对象 {
public static void main(String[] args) throws SQLException {
DBHelper dbHelper = new DBHelper();
Connection conn = dbHelper.getConnection();
String student_id = "1003";
String student_name = "陆'峰";//'峰','男',19)'
String student_sex = "男";
Integer age = 19;
//占位符与预处理对象
String sql = "INSERT INTO student (student_id,student_name,student_sex,age) VALUES (?,?,?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
//占位符的索引从1开始,当赋值的时候,系统对数据进行处理
ps.setString(1,student_id);
ps.setString(2,student_name);
ps.setString(3,student_sex);
ps.setInt(4,age);
//执行
ps.executeUpdate();
}
}
省略了自己对数据进行处理的步骤
2.防止SQL注入
该代码只是去演示如何做到防止SQL注入,实际开发中业务的逻辑不是这么写的。
package com.Demo02;
import com.yue.DBHelper;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
public class C_Statement注入 {
private static Scanner scanner = new Scanner(System.in);
public static void main(String[] args) throws SQLException {
DBHelper dbHelper = new DBHelper();
Connection conn = dbHelper.getConnection();
System.out.println("登录页面");
System.out.println("账号");
String account = scanner.next();
System.out.println("密码");
String password = scanner.next();
String sql = "SELECT * FROM userinfo where account = '"+account+"' and password = '"+password+"'";
System.out.println("sql = " + sql);
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sql);
if (rs.next()){
System.out.println("登陆成功");
}else {
System.out.println("登录失败");
}
}
}
上述演示代码就算不知道账号和密码,也能登录成功!
SQL注入,不安全
解决方式:预处理
package com.Demo02;
import com.yue.DBHelper;
import java.sql.*;
import java.util.Scanner;
public class C_PreparStatement注入 {
private static Scanner scanner = new Scanner(System.in);
public static void main(String[] args) throws SQLException {
DBHelper dbHelper = new DBHelper();
Connection conn = dbHelper.getConnection();
System.out.println("登录页面");
System.out.println("账号");
String account = scanner.next();
System.out.println("密码");
String password = scanner.next();
String sql = "SELECT * FROM userinfo WHERE account=? and password=?";
System.out.println(sql);
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1,account);
ps.setString(2,password);
ResultSet rs = ps.executeQuery();
if(rs.next()){
System.out.println("登录成功");
}else{
System.out.println("登录失败");
}
}
}
结果:
3.PrepareStatement的效率高于Statement
package com.Demo02;
import com.yue.DBHelper;
import java.sql.*;
import java.util.Random;
import java.util.Scanner;
public class E_效率 {
public static void main(String[] args) throws SQLException {
DBHelper db = new DBHelper();
Connection conn = db.getConnection();
Statement st = conn.createStatement();
long start1 = System.currentTimeMillis();
for(int i=5000;i<=6000;i++){
String studentName1 = "包子"+i;
String studentSex1 = new Random().nextBoolean()?"男":"女";
Integer age1 = new Random().nextInt(100);
String sql1 = "INSERT INTO student (student_id,student_name,student_sex,age) VALUES ('"+i+"','"+studentName1+"','"+studentSex1+"',"+age1+")";
st.executeUpdate(sql1);
}
long end1 = System.currentTimeMillis();
System.out.println("statement毫秒数 = " +(end1-start1));//35251
long start2 = System.currentTimeMillis();
String sql2 = "INSERT INTO student (student_id,student_name,student_sex,age) VALUES (?,?,?,?)";
PreparedStatement ps = conn.prepareStatement(sql2);
for(int j=8000;j<=9000;j++){
String studentName2 = "辣条"+j;
String studentSex2 = new Random().nextBoolean()?"男":"女";
Integer age2 = new Random().nextInt(100);
ps.setObject(1,j);
ps.setObject(2,studentName2);
ps.setObject(3,studentSex2);
ps.setObject(4,age2);
ps.executeUpdate();
}
long end2 = System.currentTimeMillis();
System.out.println("Preparstatement毫秒数 = " +(end2-start2));
}
}
结果:
通过反射将JDBC封装
student测试类
package com.Demo04.model;
public class Student {
private Integer student_id;
private String student_name;
private String student_sex;
private Integer age;
public Integer getStudent_id() {
return student_id;
}
public void setStudent_id(Integer student_id) {
this.student_id = student_id;
}
public String getStudent_name() {
return student_name;
}
public void setStudent_name(String student_name) {
this.student_name = student_name;
}
public String getStudent_sex() {
return student_sex;
}
public void setStudent_sex(String studentSex) {
this.student_sex = studentSex;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"studentId=" + student_id +
", student_name='" + student_name + '\'' +
", age=" + age +
", studentSex='" + student_sex + '\'' +
'}';
}
}
将记录封装为自定义java对象
package com.Demo04;
import com.Demo04.model.Student;
import com.yu.DBHelper;
import java.lang.reflect.Field;
import java.sql.*;
public class A_记录转成Java对象 {
public static void main(String[] args) throws Exception {
DBHelper dbHelper = new DBHelper();
Connection conn = dbHelper.getConnection();
String sql = "SELECT * FROM student where student_id = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1,10);
ResultSet rs = ps.executeQuery();
Student student= null;
Class clazz = Student.class;
if (rs.next()){
student = (Student) clazz.newInstance();//实例化
/* student = new Student();
//自己完成数据的存储
student.setStudentId(rs.getInt("studentId"));
student.setStudent_name(rs.getString("studentName"));
student.setStudentSex(rs.getString("studentSex"));
student.setAge(rs.getInt("studentAge"));*/
//改良代码
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
String columnName = rsmd.getColumnName(i);
Field field = getFieldObject(clazz,columnName);
if (field!=null){
Object columnValue = rs.getObject(columnName);
field.setAccessible(true);//破坏封装性,允许访问
field.set(student,columnValue);
}
}
}
System.out.println("student = " + student);
}
public static Field getFieldObject(Class clazz,String columnName){
try {
return clazz.getDeclaredField(columnName);
} catch (NoSuchFieldException e) {
return null;
}
}
}
结果:
BaseDao工具的简单封装(针对SQL单表的基础操作)
package com.yue.util;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created with IntelliJ IDEA.
* @Author: 鱼
* @Date: 2021/05/18/19:58
* @Description:针对SQL单表的基础操作
* @param <T>:传入要封装成对象的对象
* @version : 1.0
* @since 1.8
*/
public class BaseDao<T> {
private DBHelper db = DBHelper.getInstance();//单例:每次调用都返回相同的对象
private Class clazz;
public BaseDao() {
Type type = this.getClass().getGenericSuperclass();//当前堆内存中实际运行的对象的父类的泛型类型
ParameterizedType parameterizedType = (ParameterizedType) type;//强转
Type[] types = parameterizedType.getActualTypeArguments();//获取父类泛型数组
clazz = (Class) types[0];//赋值泛型中所设类型
}
/**
* 执行变更语句,使用预处理模式
*
* @param sql
* @param params
*/
public void update(String sql, Object... params) {//可变参数就是数组,数组索引从0开始,sql中索引从1开始
Connection conn = db.getConnection();
PreparedStatement ps = null;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);
}
ps.executeQuery();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(clazz.getClass() + "执行变更的SQL语句错误" + e.getMessage());
} finally {
db.close(conn,ps);
}
}
/**
* 查询结果为单条记录,封装成Map类型
*
* @param sql
* @param params
* @return java.util.Map/null
*/
public Map<String, Object> queryForMap(String sql, Object... params) {
Map<String, Object> resultMap = null;
Connection conn = db.getConnection();
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
ps.setObject(i+1, params[i]);//预处理赋值
}
rs = ps.executeQuery();//获取结果集对象
if (rs.next()) {
resultMap = new HashMap<>();//创造存储的容器
ResultSetMetaData rsmd = rs.getMetaData();//获取元信息
int columnCount = rsmd.getColumnCount();
//存储数据
for (int i = 1; i <= columnCount; i++) {
String columnName = rsmd.getColumnLabel(i);
Object colunmValue = rs.getObject(columnName);
resultMap.put(columnName,colunmValue);
}
}
return resultMap;
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(clazz.getClass() + "queryForMap,执行查询的SQL语句错误" + e.getMessage());
}finally {
db.close(conn,ps,rs);
}
}
/**
*封装多条记录
* @param sql
* @param params
* @return List<Map<String,Object>>
*/
public List<Map<String,Object>> queryForList(String sql,Object...params){
List<Map<String,Object>> resultList = null;
Connection conn = db.getConnection();
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);//预处理赋值
}
rs = ps.executeQuery();//获取结果集对象
if (rs.next()) {
resultList = new ArrayList<>();//创造存储的容器
ResultSetMetaData rsmd = rs.getMetaData();//获取元信息
int columnCount = rsmd.getColumnCount();
do{
Map<String,Object> recordMap = new HashMap<>();
//存储数据
for (int i = 1; i <= columnCount ; i++) {
String columnName = rsmd.getColumnLabel(i);
Object colunmValue = rs.getObject(columnName);
recordMap.put(columnName,colunmValue);
}
//将每条记录存储list
resultList.add(recordMap);
}while (rs.next());
}
return resultList;
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(clazz.getClass() + "queryForMap,执行查询的SQL语句错误" + e.getMessage());
}finally {
db.close(conn,ps,rs);
}
}
/**
*返回单条记录的自定义对象
* @param sql
* @param params
* @return
*/
public T queryForRecordObject(String sql, Object... params) {
T resultDate = null;
Connection conn = db.getConnection();
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);//预处理赋值
}
rs = ps.executeQuery();//获取结果集对象
if (rs.next()) {
resultDate = (T) clazz.newInstance();
ResultSetMetaData rsmd = rs.getMetaData();//获取元信息
int columnCount = rsmd.getColumnCount();
//存储数据
for (int i = 1; i <= columnCount; i++) {
String columnName = rsmd.getColumnLabel(i);
Field field = getFieldObject(columnName);
if (field!=null){
field.setAccessible(true);//破坏封装性,允许访问
Object colunmValue = rs.getObject(columnName);
field.set(resultDate,colunmValue);
}
}
}
return resultDate;
} catch (SQLException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException(clazz.getClass() + "queryForObject,执行查询的SQL语句错误" + e.getMessage());
}finally {
db.close(conn,ps,rs);
}
}
private Field getFieldObject(String columnName){
try {
return clazz.getDeclaredField(columnName);
} catch (NoSuchFieldException e) {
return null;
}
}
/**
* 返回list<T>类型
* @param sql
* @param params
* @return
*/
public List<T> queryForObjectList(String sql,Object...params){
List<T> resultList = null;
Connection conn = db.getConnection();
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
ps.setObject(i+1,params[i]);//预处理赋值
}
rs = ps.executeQuery();//获取ResultSet对象
if(rs.next()){
resultList = new ArrayList<>();
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
do{
T recordData = (T)clazz.newInstance();
for(int i=1;i <= columnCount;i++){
String columnName = rsmd.getColumnLabel(i);
Field field = getFieldObject(columnName);
if(field!=null){
field.setAccessible(true);
Object columnValue = rs.getObject(columnName);//rs.getObject(i);
field.set(recordData,columnValue);
}
resultList.add(recordData);
}
}while (rs.next());
}
return resultList;
} catch (SQLException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();//给程序员使用
throw new RuntimeException(clazz.getName()+".queryForObjectList,执行查询单条的SQL语句错误:"+e.getMessage());
}finally {
db.close(conn,ps,rs);
}
}
/**
* 返回单条记录
* @param sql
* @param params
* @return
*/
public Object queryForObject(String sql,Object...params){
Object resultObject = null;
Connection conn = db.getConnection();
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
ps.setObject(i+1,params[i]);//预处理赋值
}
rs = ps.executeQuery();//获取ResultSet对象
if(rs.next()){
resultObject = rs.getObject(1);
}
return resultObject;
} catch (SQLException e) {
e.printStackTrace();//给程序员使用
throw new RuntimeException(clazz.getName()+".queryForObject,执行查询单条的SQL语句错误:"+e.getMessage());
}finally {
db.close(conn,ps,rs);
}
}
/**
* 返回Integer类型的单条记录
* */
public Integer queryForInteger(String sql,Object...params){
Object object = queryForObject(sql,params);
if(object==null){
return null;
}
return Integer.parseInt(String.valueOf(object));
}
/**
* 返回String类型的单条记录
* */
public String queryForString(String sql,Object...params){
Object object = queryForObject(sql,params);
if(object==null){
return null;
}
return String.valueOf(object);
}
/**
* 返回类型为string的单列数据
* @param sql
* @param params
* @return
*/
public List<String> queryForStringList(String sql,Object...params){
List<String> resultList = null;
Connection conn = db.getConnection();
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
ps.setObject(i+1,params[i]);//预处理赋值
}
rs = ps.executeQuery();//获取ResultSet对象
if(rs.next()){
resultList = new ArrayList<>();
do{
resultList.add(rs.getString(1));
}while (rs.next());
}
return resultList;
} catch (SQLException e) {
e.printStackTrace();//给程序员使用
throw new RuntimeException(clazz.getName()+".queryForObject,执行查询单条的SQL语句错误:"+e.getMessage());
}finally {
db.close(conn,ps,rs);
}
}
}
getInstance的使用
- 在主函数开始时调用,返回一个实例化对象,此对象是static的,在内存中保留着它的引用,即内存中有一块区域专门用来存放静态方法和变量,
- 可以直接使用,调用多次返回同一个对象。
一般和private配合使用,使之不可被实例化,只能调用getinstance方法。
事务
概述
在工作中,一定会遇到事务方面的问题,不管任何一种语言都需对其进行处理
数据库中有事务处理。
Java通过JDBC进行事务处理
什么是事务:一个业务逻辑有多个动作组成的操作集合,当全部的操作都是正确的情况下,所有执行才起作用。如果有一个操作错误,之前所有的操作都不起作用。
1.事务处理
错误示例:
package yue.Demo01;
import com.yu.DBHelper;
import java.sql.Connection;
import java.sql.PreparedStatement;
/**
* Created with IntelliJ IDEA.
*
* @Author: 鱼
* @Date: 2021/05/20/19:25
* @Description:
*/
public class A_多个连接_事务情况 {
public static void main(String[] args) throws Exception {
DBHelper dbHelper = new DBHelper();
Connection conn = dbHelper.getConnection();
String sql1 = "INSERT INTO st(id,student_code) VALUES (?,?)";
PreparedStatement ps = conn.prepareStatement(sql1);
ps.setObject(1,"9");
ps.setObject(2,"juy");
ps.executeUpdate();//自动提交事务
String sql2 = "INSERT INTO st(id,student_code) VALUES (?,?)";
ps = conn.prepareStatement(sql2);
ps.setObject(1,"8");
ps.setObject(2,"wyzsdffasdfvs");//长度超出:Data too long for column 'student_code' at row 1
//自动提交事务
ps.executeUpdate();//执行动作失败,但是之前添加(9,juy)的数据添加成功,不符合事务要求
}
}
保证所有事务都作为一个工作单元来执行,即使出现了故障(错误),都不能改变这种执行方式,当一个事务中执行多个操作的时候,要么所有的事务都被提交(commit),要么整个事务回滚数据(rollback)到最状态。
当一个连接对象(Connection)被创建的时候,默认情况下会自动提交事务,每次执行一个SQL语句的时候,如果执行成功,就会向数据库自动提交,而不能回滚数据。
为了让多个SQl语句作为一个事务执行,步骤如下:
- 调用Connection对象的setAutoCommit(false):取消自动提交事务
- 在所有执行SQL语句都成功的时候,手动调用commit()方法,提交事务
- 在出现异常的时候,调用rollback(),方法回滚事务
- 如果此时Connection没有被关闭,则需要恢复其自动提交的状态
正确代码:
package yue.Demo01;
import com.yu.DBHelper;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* Created with IntelliJ IDEA.
*
* @Author: 鱼
* @Date: 2021/05/20/19:25
* @Description:
*/
public class B_事务处理 {
public static void main(String[] args) throws Exception {
DBHelper dbHelper = new DBHelper();
Connection conn = dbHelper.getConnection();
try {
conn.setAutoCommit(false);//1.取消自动提交功能
String sql1 = "INSERT INTO st(id,student_code) VALUES (?,?)";
PreparedStatement ps = conn.prepareStatement(sql1);
ps.setObject(1,"9");
ps.setObject(2,"juy");
ps.executeUpdate();
String sql2 = "INSERT INTO st(id,student_code) VALUES (?,?)";
ps = conn.prepareStatement(sql2);
ps.setObject(1,"8");
ps.setObject(2,"wyzsdffasdfvs");
ps.executeUpdate();
conn.commit();//2.手动提交事务
} catch (SQLException e) {
conn.rollback();//3.当有错的时候回滚数据
e.printStackTrace();
}
}
}
结果:两个操作皆未成功,数据回滚
2.批量处理JDBC
情景需要:
当我们需要成批插入或者更新记录的时候,我们可以采用Java的批量
更新机制
,该机制允许多条SQL语句一次性提交给数据库批量处理,通常该情况下比单独提交更有效率。
- JDBC的批量处理语句包括下面两个方法:
- addBatch(String):添加需要批量处理的SQL语句(Statement)或者参数(PrepareStatement)
- executeBatc() :执行批量处理语句
- 通常我们会遇到两种批量处理SQL的情况:
- 处理多条SQL语句的批量
- 一个SQL语句有批量的参数
Statement:处理多条SQL
public class C_批量处理语句 {
public static void main(String[] args) throws Exception {
DBHelper dbHelper = new DBHelper();
Connection conn = dbHelper.getConnection();
try {
conn.setAutoCommit(false);//取消自动提交功能
long start = System.currentTimeMillis();
Statement statement = conn.createStatement();
//批量处理多条SQL语句
for(int i=1;i<=1000;i++){
String sql = "INSERT INTO st(id,student_code) VALUES ('叶凡"+i+"','789')";
statement.addBatch(sql);
}
statement.executeBatch();
long end = new Date().getTime();
System.out.println(end-start+"ms");//
} catch (SQLException e) {
conn.rollback();//3.回滚数据
e.printStackTrace();
}
}
}
PrepareStatement:处理批量参数
public class D_批量参数 {
public static void main(String[] args) throws Exception {
DBHelper dbHelper = new DBHelper();
Connection conn = dbHelper.getConnection();
try {
conn.setAutoCommit(false);//取消自动提交功能
long start = System.currentTimeMillis();
String sql = "INSERT INTO userinfo (account,password) VALUES (?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
//一条SQL批量处理参数
for(int i=1;i<=1000;i++){
ps.setObject(1,"姬明月"+i);
ps.setObject(2,"789");
ps.addBatch();
}
ps.executeBatch();
long end = new Date().getTime();
System.out.println(end-start+"ms");//16256ms
} catch (SQLException e) {
conn.rollback();//3.回滚数据
e.printStackTrace();
}
}
}
一般后者效率更快