JDBC学习笔记
浏览器端-------->HTML------>CSS
-------->JavaScript------->jQuery
服务器端----------->Tomcat------>XML
------->Servlet:处理业务逻辑
1、jdbc概述
1.1、数据的持久化
- 持久化:把数据保存到可掉电式存储设备中以供之后使用。
1.5、jdbc连接程序编写步骤
package com.company.connectionTest;
import com.mysql.jdbc.Driver;
import org.junit.Test;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class connectionTest {
public connectionTest() {
}
@Test
public void testConnection1() {
Object driver = null;
try {
new Driver();
} catch (SQLException var8) {
String url = "jdbc:mysql://localhost:3306/jdbc";
Properties info = new Properties();
info.setProperty("user", "root");
info.setProperty("password", "123456");
Connection conn = null;
try {
conn = ((java.sql.Driver)driver).connect(url, info);
} catch (SQLException var7) {
var7.printStackTrace();
}
System.out.println(conn);
}
}
@Test
public void testConnection2() throws Exception {
Class myclass = Class.forName("com.mysql.jdbc.Driver");
java.sql.Driver driver = (java.sql.Driver)myclass.newInstance();
String url = "jdbc:mysql://localhost:3306/jdbc";
Properties info = new Properties();
info.setProperty("user", "root");
info.setProperty("password", "123456");
Connection conn = driver.connect(url, info);
System.out.println(conn);
}
@Test
public void testConnection3() throws Exception {
//加载Driver
Class myclass = Class.forName("com.mysql.jdbc.Driver");
java.sql.Driver driver = (java.sql.Driver)myclass.newInstance();
//提供三个连接的基本信息
String url = "jdbc:mysql://localhost:3306/jdbc";
String user = "root";
String password = "123456";
//注册驱动
DriverManager.registerDriver(driver);
//获取连接
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
}
//方式四:可以只是加载驱动,不用显示的注册驱动了
@Test
public void testConnection4() throws Exception {
//1、提供三个连接的基本信息
String url = "jdbc:mysql://localhost:3306/jdbc";
String user = "root";
String password = "123456";
//2、加载Driver
Class myclass = Class.forName("com.mysql.jdbc.Driver");
//相较于第三种方式,可以省略如下操作:
//Driver driver = (Driver) myclass.newInstance();
//DriverManager.registerDriver(driver);
/*
* 以上方法可以省略的原因是
* static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
* */
//获取链接
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
}
//最终版:将数据库连接需要的4个基本信息声明在配置文件中,通过读取配置文件的方式,获取连接
//优点:
// 实现了数据和代码的分离,实现了解耦
// 如果需要修改配置文件信息,可以避免程序重新打包
//
@Test
public void testConnection5() throws Exception {
//1、读取配置文件中的4个基本信息
InputStream in = connectionTest.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties();
properties.load(in);
String driverClass = properties.getProperty("driverClass");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
//加载驱动
Class.forName(driverClass);
//建立连接
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
}
}
2、使用PreparedStatement实现CURD操作
2.1、操作和访问数据库
- 数据库连接被用于向数据库服务器发送命令和SQL语句,并接受数据库服务器返回结果。其实一个数据库连接就是一个Socket连接
- 在java.sql包中有3个接口分别定义了数据库的调用的不同方式:
- Statement:用于执行静态SQL语句并返回它所生成结果对象。
- PrePatedStatement:SQL语句被预编译并存储在此对象中,可以使用此对象多次高效的执行该语句。
- CallableStatemenet:用于执行SQL存储过程
2.2、使用Statement操作数据表的弊端
-
通过调用Connection对象的createStatement()方法创建该对象,该对象用于执行静态的SQL语句,并且返回执行结果。
-
Statement接口中定义了下列方法用于执行SQL语句:
int excuteUpdate(String sql);执行更新操作的INSERT、UPDATE、DELETE ResultSet executeQuery(String sql);执行查询操作的SELECT
-
但是使用Statement操作数据表存在弊端:
- 问题1:存在拼串操作,繁琐
- 问题2:存在SQL注入问题
-
SQL注入是通过利用某些系统美哟有对用户输入数据进行充分色检查,而在用户输入数据中注入非法的sql语句段或命令(如:SELECT USER,PASSSWORD FROM USER_TABLE WHERE USRE=‘a’ OR 1=‘AND PASSWORD=’ OR ‘1’=‘1’)从而利用系统是我SQL引擎完成恶意行为的做法
-
对于java而言,要防范SQL注入,只要用PreparedStatement(从Statement扩展而来)取代Statement就可以了
2.3、向表中添加一条数据
package com.company.preparedstatement.crud;
/*
*使用PreparedStatement来替换Statement,
* 实现对数据表的增删改操作
*
*/
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Properties;
public class PreparedStatementUpdateTest {
@Test
//course添加一条数据
public void test(){
Connection connection = null;
PreparedStatement ps = null;
InputStream is = null;
try {
is = ClassLoader.getSystemClassLoader().getResourceAsStream("db.properties");
Properties pros = new Properties();
pros.load(is);
String driverClass = pros.getProperty("driverClass");
String url = pros.getProperty("url");
String user = pros.getProperty("user");
String password = pros.getProperty("password");
//2.加载驱动
Class.forName(driverClass);
//3.获取连接
connection = DriverManager.getConnection(url, user, password);
//System.out.println(connection);
//4.预编译sql语句,返回PreparedStatement的实例
String sql = "insert into course(CId,Cname,TId)values(?,?,?)";
ps = connection.prepareStatement(sql);
//5.填充占位符
ps.setString(1,"06");
ps.setString(2,"英语");
ps.setString(3,"03");
//执行操作
ps.execute();
} catch (IOException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally {
//关闭连接
try {
if(ps != null){
ps.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2.4、通用的增删改操作
//增删改通用操作
@Test
//传入一个sql语句和占位符相对应的内容,因为不知道有多少个占位符,每个占位符都是什么类型的,因此使用Object... agrs表示随机参数
public void test2(String sql,Object... agrs) {
Connection connection = null;
PreparedStatement ps = null;
try {
//1.获取数据库连接
connection = JDBCUtils.getConnection();
//2.预编译sql语句,返回PrepareStatement的实例
ps = connection.prepareStatement(sql);
//3.添加占位符
for(int i = 0;i < agrs.length;i++){
ps.setObject(i + 1,agrs[i]);
}
//4.执行
ps.execute();
} catch (Exception e) {
e.printStackTrace();
}finally {
//5.关闭资源
JDBCUtils.closeResource(connection,ps);
}
}
2.5、查询一个表中的一条指定数据
@Test
public void test3() {
Connection connection = null;
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
//1.获取数据库连接
connection = JDBCUtils.getConnection();
String sql = "select * from jdbc.course where CId = ?";
//2.预编译sql语句,返回PrepareStatement的实例
ps = connection.prepareStatement(sql);
//3.填充占位符
ps.setObject(1,"06");
//4.执行并返回结果集
resultSet = ps.executeQuery();
//判断结果集是否有数据存在
if(resultSet.next()){
//next():判断结果集的下一条数据是否有数据,有的话返回true没有则不进入if
//获取当前这条数据的字段值
String cid = resultSet.getString(1);
String cname = resultSet.getString(2);
String tid = resultSet.getString(3);
course cs = new course(cid,cname,tid);
System.out.println(cs);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//5.关闭资源
JDBCUtils.closeResource(connection,ps,resultSet);
}
}
2.6、一张表中通用的查询操作
package com.company.preparedstatement.crud;
import com.company.preparedstatement.Utils.JDBCUtils;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
public class Test {
//针对一张表查询一条数据的通用方法
public course test4(String sql,Object... args) {
Connection connection = null;
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
//创建连接
connection = JDBCUtils.getConnection();
//获取对象
ps = connection.prepareStatement(sql);
//获取占位符对应的值
for(int i = 0;i < args.length;i++){
ps.setObject(i+1,args[i]);
}
//执行返回结果集
resultSet = ps.executeQuery();
//获取结果集的元数据:ResultSetMetaData
ResultSetMetaData metaData = resultSet.getMetaData();
//获取字段值个数
int columnCount = metaData.getColumnCount();
//判断有没有返回值
while (resultSet.next()){
//创建一个对象
course cs = new course();
//处理一行数据中的每一个列
for(int i = 0;i < columnCount;i++){
//获取列值
Object columValue = resultSet.getObject(i + 1);
//获取每个列的列名
String columName = metaData.getColumnName(i + 1);
//给对象中的给对象中columName属性用set方法赋值为columvalue:通过反射实现
//通过反射获取属性
Field declaredField = course.class.getDeclaredField(columName);
//属性值可能是私有的,设置一下
declaredField.setAccessible(true);
//给对象的属性赋值
declaredField.set(cs,columValue);
}
System.out.println(cs);
//返回对象
//return cs;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//关闭资源
JDBCUtils.closeResource(connection,ps,resultSet);
return null;
}
}
@org.junit.Test
public void test44(){
String sql = "select CId,Cname,TId from course where Cname=?";
course course = test4(sql, "英语");
System.out.println(course);
}
}
注意,在这段代码中,对应的字段名和属性名必须完全一样
2.7、字段名和属性名不一样时
针对属性名和字段名不一样的解决方法:
我们知道数据库字段名和java类中的属性名通常情况下都不一样,在反射过程中,需要保证二者一样才能保证反射获取属性的set方法成功,那如何在二者名称不一样的情况下又成功反射到set方法呢?
我们可以使用ResultSetMetaData时,需要使用getColumnLable()来替换getColumnName()获取列的别名
说明:如果sql中没有给字段名,getColunmnLabel()获取的就是列名
2.8、针对于不同表通用的查询一条数据的操作(该代码,有且只能返回一条数据)
初步想法,不同的表代表java中不同的实体类,如何在同一个方法中获取不同的类,使用关于Class和Method的反射,方法参数应该也要加一个实体类的类名,用于对应的实体类和对应的表建立连接
难点:course cs = new course();如何知道我们查询的表对应的是哪个实体类
public class QueryTest {
//Object不好,只需要传进要给的类名,不需要传进来一个实例化对象
//代码改进,老师写的
public <T> T queryTest(String sql,Class<T> clazz,Object... args){
//获取数据库连接
Connection connection = null;
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
connection = JDBCUtils.getConnection();
//获取PreparedStatement对象
ps = connection.prepareStatement(sql);
//填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1,args[i]);
}
//执行
resultSet = ps.executeQuery();
//获取结果集的元数据
ResultSetMetaData metaData = resultSet.getMetaData();
//获取结果集中的字段个数
int columnCount = metaData.getColumnCount();
if(resultSet.next()){
//用传入的类,创建实例化对象,对象类型为泛型T
T instance = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
//获取字段别名,如果没有别名就获取字段列名
String columnLabel = metaData.getColumnLabel(i + 1);
//通过字段的先后顺序获取结果集中的结果值
//获取字段别名对应的值
Object value = resultSet.getObject(i + 1);
//Field declaredField = course.class.getDeclaredField(columName);
//解决course.class中course.class可变的事情
//使用用户传进来的对象.getClass()方法获取实体类的class文件从而反射到字段名对应的属性名
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(instance,value);
}
System.out.println(instance);
//return instance;
}
}catch (Exception e) {
e.printStackTrace();
}finally {
//关闭资源
JDBCUtils.closeResource(connection,ps,resultSet);
}
return null;
}
//自己写的
public Object queryTest(String sql,Object className,Object... args) {
//获取数据库连接
Connection connection = null;
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
connection = JDBCUtils.getConnection();
//获取PreparedStatement对象
ps = connection.prepareStatement(sql);
//填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1,args[i]);
}
//执行
resultSet = ps.executeQuery();
//获取结果集的元数据
ResultSetMetaData metaData = resultSet.getMetaData();
//获取结果集中的字段个数
int columnCount = metaData.getColumnCount();
while(resultSet.next()){
for (int i = 0; i < columnCount; i++) {
//获取字段别名,如果没有别名就获取字段列名
String columnLabel = metaData.getColumnLabel(i + 1);
//通过字段的先后顺序获取结果集中的结果值
//获取字段别名对应的值
Object value = resultSet.getObject(i + 1);
//Field declaredField = course.class.getDeclaredField(columName);
//解决course.class中course.class可变的事情
//使用用户传进来的对象.getClass()方法获取实体类的class文件从而反射到字段名对应的属性名
Field field = className.getClass().getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(className,value);
}
//System.out.println(className);
return className;
}
}catch (Exception e) {
e.printStackTrace();
}finally {
//关闭资源
JDBCUtils.closeResource(connection,ps,resultSet);
}
return null;
}
@Test
public void test(){
//用户输入查询语句
String sql = "select CId,Cname,TId from jdbc.course where CId = ?";
//用户创建要查询的实体类对象
course cs = new course();
//把sql语句,对象名,占位符要填充的内容传入要调用的方法内
Object o = queryTest(sql, cs, "06");
//把sql语句,类.class,和占位符要填充的内容传入要调用的方法内
Object o = queryTest(sql, course.class, "06");
System.out.println(o);
}
}
2.9、针对不同的表通用查询返回多条数据
public <T> List<T> queryTest(String sql,Class<T> clazz,Object... args){
//获取数据库连接
Connection connection = null;
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
connection = JDBCUtils.getConnection();
//获取PreparedStatement对象
ps = connection.prepareStatement(sql);
//填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1,args[i]);
}
//执行
resultSet = ps.executeQuery();
//获取结果集的元数据
ResultSetMetaData metaData = resultSet.getMetaData();
//获取结果集中的字段个数
int columnCount = metaData.getColumnCount();
List<T> list= new ArrayList<T>();
while(resultSet.next()){
//用传入的类,创建实例化对象,对象类型为泛型T
T instance = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
//获取字段别名,如果没有别名就获取字段列名
String columnLabel = metaData.getColumnLabel(i + 1);
//通过字段的先后顺序获取结果集中的结果值
//获取字段别名对应的值
Object value = resultSet.getObject(i + 1);
//使用用户传进来的对象.getClass()方法获取实体类的class文件从而反射到字段名对应的属性名
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(instance,value);
}
list.add(instance);
}
return list;
}catch (Exception e) {
e.printStackTrace();
}finally {
//关闭资源
JDBCUtils.closeResource(connection,ps,resultSet);
}
return null;
}
@Test
public void test(){
//用户输入查询语句
String sql = "select CId,Cname,TId from jdbc.course where CId = ?";
List<course> courses = queryTest(sql, course.class, "06");
for (course cours : courses) {
System.out.println(cours);
}
}
2.10、PreparedStatement解决sql注入的问题
PreparedStatement会将SQL语句进行预编译,从而解决sql注入问题。
同时,PreparedStatement还可以进行高效的批量操作。
3、JDBC API小结
-
两种思想
-
面向接口编程
-
ORM思想(object relational mapping)
- 一个数据表对应一个java类
- 表中的一条记录对应java类的一个对象
- 表中的一个字段对应java类的一个属性
sql是需要结合列名和表的属性名来写的,注意经常要起别名
-
-
两种技术
- JDBC结果集的元数据:ResultSetMetaData
- 获取列数:getColumnCount()
- 获取列表别名:getColumnLabel();
- 通过反射,创建指定类对象,获取指定的属性并赋值
- JDBC结果集的元数据:ResultSetMetaData
4、练习
4.1、在控制台中输入数据向一张表中插入一个数据
package com.company.preparedstatement;
import com.company.preparedstatement.Utils.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.Scanner;
//从控制台向数据库jdbc.Student表中插入一条数据
public class pretiect {
//通用的添加数据的方法
public void add(String sql,Object... agrs) {
//连接数据库
Connection connection = null;
PreparedStatement ps = null;
try {
connection = JDBCUtils.getConnection();
//获取对象
ps = connection.prepareStatement(sql);
//填充占位符
for (int i = 0; i < agrs.length; i++) {
ps.setObject(i+1,agrs[i]);
}
//执行
ps.execute();
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.closeResource(connection,ps);
}
}
public static void main(String[] args) {
//控制台输入信息
Scanner scanner = new Scanner(System.in);
System.out.println("请输入id:");
String id = scanner.next();
System.out.println("请输入name:");
String name = scanner.next();
System.out.println("请输入生日:");
String birth = scanner.next();
System.out.println("请输入性别:");
String sex = scanner.next();
String sql = "insert into jdbc.Student(SId,Sname,Sage,Ssex) values (?,?,?,?)";
new pretiect().add(sql,id,name,birth,sex);
}
}
问题:在添加方法处理异常之后如何判断是否插入成功,需要执行完之后返回一个值用来判断是否执行成功。
ps.execute();返回值
如果执行的是查询操作,有返回结果,此方法返回true;
如果执行是增、删、改操作没有返回结果,则此方法返回false
int i = ps.executeUpdate();//表示影响了几条数据
通过ps.executeUpdate();的返回值我们可以判断sql;语句是否执行成功!
后代码可以改为
package com.company.preparedstatement;
import com.company.preparedstatement.Utils.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.Scanner;
//从控制台向数据库jdbc.Student表中插入一条数据
public class pretiect {
//通用的添加数据的方法
public int add(String sql,Object... agrs) {
//连接数据库
Connection connection = null;
PreparedStatement ps = null;
try {
connection = JDBCUtils.getConnection();
//获取对象
ps = connection.prepareStatement(sql);
//填充占位符
for (int i = 0; i < agrs.length; i++) {
ps.setObject(i+1,agrs[i]);
}
//执行
int i = ps.executeUpdate();
return i;
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.closeResource(connection,ps);
}
return 0;
}
public static void main(String[] args) {
//控制台输入信息
Scanner scanner = new Scanner(System.in);