目录
⚪在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式
三、使用PreparedStatement替换Statement,实现对数据表的增删改查操作
一、操作和访问数据库
数据库连接被用于向数据库服务器发送命令和 SQL 语句,并接受数据库服务器返回的结果。
一个数据库连接就是一个Socket连接。
⚪在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式
- Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
- PrepatedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
- CallableStatement:用于执行 SQL 存储过程
二、使用Statement操作数据表的弊端
- 问题一:存在拼串操作,繁琐
- 问题二:存在SQL注入问题
SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令,从而利用系统的 SQL 引擎完成恶意行为的做法。
对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement(从Statement扩展而来) 取代 Statement 就可以了。
三、使用PreparedStatement替换Statement,实现对数据表的增删改查操作
1.添加数据
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.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;
public class PreparedStatementUpdateTest {
//向departments中添加一条记录
@Test
public void test1(){
Connection connection = null;
PreparedStatement ps = null;
try {
//1.读取配置文件中的4个基本信息
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
pros.load(is);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("driverClass");
//2.加载驱动
Class.forName(driverClass);
//3.获取连接
connection = DriverManager.getConnection(url, user, password);
//4.预编译sql语句,返回PreparedStatement实例
String sql = "insert into departments(department_id,department_name,manager_id,location_id)values(?,?,?,?)";//?:占位符
ps = connection.prepareStatement(sql);
//5.填充占位符
ps.setString(1,"280");
ps.setString(2,"shopping");
ps.setString(3,"260");
ps.setString(4,"1800");
//如果需要插入时间
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// java.util.Date date = sdf.parse("1000-01-01");
// ps.setDate(4, (java.sql.Date) new Date(date.getTime()));
//6.执行操作
ps.execute();
} catch (Exception e) {
e.printStackTrace();
}finally{
//7.资源的关闭
try {
if(ps != null)
ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if(connection != null)
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
上述操作会报错,因为departments没有主键约束或唯一性约束
⚪封装数据库连接和关闭操作
在新建的包中
import connection.ConnectionTest;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
//操作数据库的工具类
public class JDBCUtils {
//获取数据库的连接
public static Connection getConnection() throws Exception {
//1.读取配置文件中的4个基本信息
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
//获取系统类加载器
Properties pros = new Properties();
pros.load(is);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("driverClass");
//2.加载驱动
Class.forName(driverClass);
//3.获取连接
Connection connection = DriverManager.getConnection(url, user, password);
return connection;
}
//关闭连接
public void closeResource(Connection conn, Statement ps){
try {
if(ps != null)
ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if(conn != null)
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
2.修改数据
//修改countries表中的一条数据
@Test
public void test2(){
Connection conn = null;
PreparedStatement ps = null;
try {
//1.获取数据库的连接
conn = JDBCUtils.getConnection();
//2.预编译sql语句,返回PreparedStatement实例
String sql = "update countries set country_name = ? where country_id = ?";
ps = conn.prepareStatement(sql);
//3.填充占位符
ps.setObject(1,"CHINA");
ps.setObject(2,"CH");
//4.执行
ps.execute();
} catch (Exception e) {
e.printStackTrace();
}finally{
//5.资源的关闭
JDBCUtils.closeResource(conn,ps);
}
}
3.通用的增删改操作
public void update(String sql,Object ...args){//sql中占位符的个数与可变形参的长度相同
Connection conn = null;
PreparedStatement ps = null;
try {
//1.获取数据库的连接
conn = JDBCUtils.getConnection();
//2.预编译sql语句,返回PreparedStatement实例
ps = conn.prepareStatement(sql);
//3.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1,args[i]); //小心参数声明错误
}
//4.执行
ps.execute();
} catch (Exception e) {
e.printStackTrace();
}finally{
//5.关闭资源
JDBCUtils.closeResource(conn,ps);
}
}
删除测试
@Test
public void test3(){
//删除操作
String sql = "delete from countries where country_id = ?";
update(sql,"JP");
}
4.查询数据
例1:针对于Customers表的查询操作
@Test
public void test1(){
Connection conn = null;
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
conn = JDBCUtils.getConnection();
String sql = "select id,name,email,birth from customers where id = ?";
ps = conn.prepareStatement(sql);
ps.setObject(1,1);
//执行,并返回结果集
resultSet = ps.executeQuery();
//处理结果集
if (resultSet.next()){//next():判断结果集的下一条是否有数据,如果有数据,返回true并指针下移;如果返回false,指针不会下移
//获取当前这条数据的各个字段值
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
String email = resultSet.getString(3);
Date birth = resultSet.getDate(4);
//方式一:
// System.out.println("id = " + id + ",name = " + name + ",email = " + email +",birth = " + birth);
//方式二:
// Object[] data = new Object[]{id,name,email,birth};
//方式三:将数据封装为一个对象(推荐方式)
Customer customer = new Customer(id,name,email,birth);
System.out.println(customer);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//关闭资源
JDBCUtils.closeResource(conn,ps,resultSet);
}
}
package bean;
import java.util.Date;
//ORM编程思想(object relational mapping —— 对象关系映射)
public class Customer {
private int id;
private String name;
private String email;
private Date birth;
public Customer() {
}
public Customer(int id, String name, String email, Date birth) {
this.id = id;
this.name = name;
this.email = email;
this.birth = birth;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
@Override
public String toString() {
return "Customer{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
", birth=" + birth +
'}';
}
}
//关闭资源的操作
public static void closeResource(Connection conn, Statement ps, ResultSet rs){
try {
if(ps != null)
ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if(conn != null)
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if(rs != null)
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
例2:针对于customers表的通用的查询操作
获取结果集的元数据:
ResultSetMetaData rsmd = rs.getMetaData();
通过获取结果集中的元数据获取结果集的列数
public Customer queryForCustomers(String sql,Object ...args){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
rs = ps.executeQuery();
//获取结果集的元数据
ResultSetMetaData rsmd = rs.getMetaData();
//通过获取结果集中的元数据获取结果集的列数
int columnCount = rsmd.getColumnCount();
if (rs.next()){
Customer cust = new Customer();
//处理一行数据中的每一个列
for (int i = 0; i < columnCount; i++) {
//获取列值
Object columnValue = rs.getObject(i + 1);//获取到字段的值
//获取每个列的列名
String columnName = rsmd.getColumnName(i + 1);
//给cust对象指定的columnName属性赋值为columnValue:通过反射
Field field = Customer.class.getDeclaredField(columnName);
field.setAccessible(true);
field.set(cust,columnValue);
}
return cust;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,ps,rs);
}
return null;
}
测试
@Test
public void test4(){
String sql = "select id,name,birth,email from customers where id = ?";
Customer customer = queryForCustomers(sql, 13);
System.out.println(customer);
}
⚪图解查询操作的流程 ![](https://i-blog.csdnimg.cn/blog_migrate/195eab1c51d2b1a201d2535e50802344.png)
例3:针对于不同表的查询操作
⚪针对于不同的表的通用的查询操作,此时返回一个表中的一条记录
import bean.Customer;
import com.mysql.cj.x.protobuf.MysqlxCrud;
import org.junit.Test;
import util.JDBCUtils;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
public class PreparedStatementQueryTest {
@Test
public void test1(){
String sql = "select id,name,email from customers where id = ?";
Customer customer = getInstance(Customer.class, sql, 12);
System.out.println(customer);
String sql1 = "select order_id orderId,order_name orderName from `order` where order_id = ?";
Order order = getInstance(Order.class, sql1, 1);
System.out.println(order);
}
public <T> T getInstance(Class<T> clazz,String sql,Object ...args){ //使用泛型方法
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
rs = ps.executeQuery();
//获取结果集的元数据
ResultSetMetaData rsmd = rs.getMetaData();
//通过获取结果集中的元数据获取结果集的列数
int columnCount = rsmd.getColumnCount();
if (rs.next()){
T t = clazz.newInstance();
//处理一行数据中的每一个列
for (int i = 0; i < columnCount; i++) {
//获取列值
Object columnValue = rs.getObject(i + 1);//获取到字段的值
//获取每个列的列名
String columnName = rsmd.getColumnName(i + 1);
//给t对象指定的columnName属性赋值为columnValue:通过反射
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t,columnValue);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,ps,rs);
}
return null;
}
}
⚪ 针对于不同的表的通用的查询操作,此时返回一个表中的多条记录
@Test
public void test2(){
String sql = "select id,name,email from customers where id < ?";
List<Customer> list = getForList(Customer.class, sql, 12);
list.forEach(System.out::println);
}
public <T> List<T> getForList(Class<T> clazz,String sql,Object ...args){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
rs = ps.executeQuery();
//获取结果集的元数据
ResultSetMetaData rsmd = rs.getMetaData();
//通过获取结果集中的元数据获取结果集的列数
int columnCount = rsmd.getColumnCount();
//创建集合对象:给t对象指定的属性赋值
ArrayList<T> list = new ArrayList<>();
while (rs.next()){
T t = clazz.newInstance();
//处理一行数据中的每一个列
for (int i = 0; i < columnCount; i++) {
//获取列值
Object columnValue = rs.getObject(i + 1);//获取到字段的值
//获取每个列的列名
String columnName = rsmd.getColumnName(i + 1);
//给t对象指定的columnName属性赋值为columnValue:通过反射
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t,columnValue);
}
list.add(t);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,ps,rs);
}
return null;
}
①ORM编程思想
object relational mapping:对象关系映射
一个数据表对应一个Java类
表中的一条记录对应Java类的一个对象
表中的一个字段对应Java类的一个属性
②java与sql对应数据类型转换表 ![](https://i-blog.csdnimg.cn/blog_migrate/ca0d0e000e6b9d19d3a4a9b50d3f1b31.png)
③ResultSet与ResultSetMetaData
Ⅰ.ResultSet
- 查询需要调用PreparedStatement 的 executeQuery() 方法,查询结果是一个ResultSet 对象
- ResultSet 对象以逻辑表格的形式封装了执行数据库操作的结果集,ResultSet 接口由数据库厂商提供实现
- ResultSet 返回的实际上就是一张数据表。有一个指针指向数据表的第一条记录的前面。
- ResultSet 对象维护了一个指向当前数据行的游标,初始的时候,游标在第一行之前,可以通过 ResultSet 对象的 next() 方法移动到下一行。调用 next()方法检测下一行是否有效。若有效,该方法返回 true,且指针下移。 相当于Iterator对象的 hasNext() 和 next() 方法的结合体。
- 当指针指向一行时, 可以通过调用 getXxx(int index) 或 getXxx(int columnName) 获取每一列的值。
注意:Java与数据库交互涉及到的相关Java API中的索引都从1开始。
Ⅱ.ResultSetMetaData
可用于获取关于 ResultSet 对象中列的类型和属性信息的对象
ResultSetMetaData meta = rs.getMetaData();
- getColumnName(int column):获取指定列的名称
- getColumnLabel(int column):获取指定列的别名
- getColumnCount():返回当前 ResultSet 对象中的列数。
- getColumnTypeName(int column):检索指定列的数据库特定的类型名称。
- getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。
- isNullable(int column):指示指定列中的值是否可以为 null。
- isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。