JDBC、PreparedStatement、代理模式

这篇博客详细介绍了JDBC的基本操作,包括加载数据库驱动、获取连接、执行增删改查操作和事务管理。接着讲解了PreparedStatement,它是解决SQL注入问题的有效手段,具有预编译和缓存功能。最后探讨了代理模式,解释了动态代理的概念,如何通过Proxy类和InvocationHandler接口创建并使用代理对象。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JDBC

客户端操作MYSQL数据库的方式:

  1. 通过命名行登录数据库。
  2. 通过图形化软件登录数据库,比如Sqlyog,Navicate...
  3. 通过java程序登录数据库并操作数据库。

什么是JDBC:

  • Java DataBase Connetivity Java数据库连接技术。

JDBC概述:

  • jdbc主要用于java代码连接数据库,java代码就可以发送sql语句给数据库服务器,操作数据库中数据。

JDBC的作用:

  • 用来连接数据库并对数据库进行增删改查操作。

JDBC的好处:

  • 可以写好一份操作数据库的代码之后,不需要怎么修改代码就可以访问各种类型的数据库。

JDBC原理图:

JDBC核心API概述:

  • DriverManager:类  用来加载和注册驱动的以及获得连接对象。
  • Driver:接口  驱动接口。
  • Conncetion:接口  数据库连接对象,用来负责和数据库服务器进行连接的。
  • Statement:接口  SQL语句发送对象,用来将SQL语句发送给数据库执行并获得执行结果。
  • ResultSet:接口  结果集对象,用来封装查询到的结果。

JDBC获取连接

概述:

  • Connection表示java程序与数据库之间的连接,只有拿到Connection才能操作数据库。

JDBC获取连接步骤

  1. 导入驱动jar包
  2. 注册驱动
  3. 获取连接

加载数据库的驱动类

方式一:(推荐

Class.forName("com.mysql.jdbc.Driver");

注意事项:

  • 从JDK1.5开始,Class.forName这句话可以 不写。考虑到系统兼容性问题,所以还是建议写上。
  • 加载Driver类的时候,其实它目的是为了执行该类的静态代码块的代码。而静态代码块的代码出现了DriverManager。

方式二:

前提:

  • 加载Driver类的时候,其实它目的是为了执行该类的静态代码块的代码。而静态代码块的代码出现了DriverManager。

DriverManager类与注册驱动相关方法:

static void registerDriver(Driver driver)

  • 注册数据库驱动。

示例代码:

public class JDBCDemo01 {
/*
注册驱动方式2:驱动只需要注册一个就可以了(推荐使用)
*/
@Test
public void test02()throws Exception{
// 注册驱动
Class.forName("com.mysql.jdbc.Driver");
}
/*
注册驱动方式1:存在一个问题:驱动会注册两次
*/
@Test
public void test01()throws Exception{
/* 创建驱动类对象时会触发Driver类静态代码块的执行,在Driver类的静态代码块已经注
册了了一次驱动了。*/
Driver driver = new Driver();
// 注册驱动
DriverManager.registerDriver(driver);
}
}

获取连接

DriverManager类与获得连接对象相关的方法:

static Connection getConnection(String url, Properties info)

  • 获得连接对象
  • url:连接字符串
  • info:属性集合,用来存储用户名和密码。

static Connection getConnection(String url, String user, String password)

  • 获得连接对象
  • url:连接URL字符串
  • user:用户名
  • password:密码

数据库连接URL字符串的格式:

  • jdbc协议:子协议://数据库的地址:数据库的端口号/数据库名
  • jdbc协议:固定值:jdbc
  • 自协议:数据库厂商名称,mysql

MYSQL数据库的连接字符串的格式

  • jdbc:mysql://localhost:3306/数据库名
  • 若是本机地址且端口号是默认的端口号,可以写成:jdbc:mysql:///数据库名

如果数据乱码需要加上参数:?characterEncoding=utf8

  • 表示让数据库以utf8编码处理数据
  • 示例:jdbc:mysql://localhost:3306/数据库名?characterEncoding=utf8

示例代码:

public class JDBCDemo01 {
public static void main(String[] args)throws Exception{
// 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 创建属性集合对象
Properties info = new Properties();
// 创建字节输入流关联⽬标文件
FileInputStream fis = new FileInputStream("day18_课堂代码/jdbc.properties");
// 从文件中加载配置信息到集合中
info.load(fis);
// 关闭流
fis.close();
System.out.println(info);
// 获得连接对象
Connection conn =
DriverManager.getConnection("jdbc:mysql://localhost:3306/day18",info);
System.out.println(conn);
}
@Test
public void test01()throws Exception{
// 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 获得连接对象
Connection conn =
DriverManager.getConnection("jdbc:mysql://localhost:3306/day18", "root", "root");
System.out.println(conn);
}
}

JDBC实现对单表数据增、删、改

  • 我们要对数据库进行增删改查,需要使用statement对象来执行SQL语句。

Connection对象

注意:

  • Connection、Statement、resultSet都是接口。

Connection对象常用方法:

statement createStatement()

  • 获取到Statement(SQL发送对象)。

Statement对象

statement对象概述:

  • SQL语句发送对象:用来将SQL语句发送给数据库执行并获得执行结果。

Statement对象的常用方法:

boolean excute(String sql)

  • 此方法可以执行任意sql语句,返回boolean值,表示是否返回ResultSet结果集。仅当执行select语句,且有返回结果时返回true,其他语句都返回false。一般用指向DDL语句。

int executeUpdate(String sql)

  • 用来执行增删改语句,只要不是查询都可以使用该方法执行,一般指向DML语句。
  • 返回影响的行数。

ResultSet executeQuery(String sql)

  • 用来执行查询语句,将查询结果封装到ResultSet。(只能执行select语句

示例代码:

public void testInsert()throws Exception{
// 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 获得连接对象
Connection conn =
DriverManager.getConnection("jdbc:mysql://localhost:3306/day18", "root", "root");
// 获得SQL语句句发送对象
Statement stmt = conn.createStatement();
// 准备SQL语句句
String sql = "insert into student(name,gender,birthday) values" +
"('rose','⼥女女','1999-02-10')" +
",('jack','男','2000-05-10')" +
",('lily','⼥女女','2010-09-20');";
// 执⾏行行SQL语句句
int row = stmt.executeUpdate(sql);
System.out.println("row = " + row);
// 关闭资源
stmt.close();
conn.close();
}

JDBC实现对单表数据查询

ResultSet对象

ResultSet对象的作用:

  • 用来封装查询结果集。

ResultSet的原理:

  1. ResultSet内部有一个指针,刚开始记录开始位置。
  2. 调用next方法,ResultSet内部指针会移动到下一行数据。
  3. 我们可以通过ResultSet得到一行数据,getXxx()得到某列数据。
  • 其实任何类型数据都可以用String getString( 列号或列名)获取。把该类型转换成字符串,但是不建议这样使用。

ResultSet接口使用注意事项:

如果指针指向第一行记录前面调用getXxx方法则会抛出该异常:

  • java.sql.SQLException: Before start of result set

如果指针指向最后一行记录后面调用getXxx方法则会抛出该异常:

  • java.sql.SQLException: After end of result set

ResultSet接口常用方法:

boolean next();

  • 将指针下移一行并判断当前指向行的位置是否有记录,如果有记录则返回true,否则false。

Xxx getXxx(列号或列名);

  • 根据列号或列名获得对应的数据。
  • 如果指定的是列号,默认是从1开始。

注意事项

  • Connection、Statement、resultSet用完都需要关闭资源。
  • 关闭原则:先开后关,后开先关。

JDBC开发步骤小结:

  1. 通过Class.forName("驱动类全名")注册数据库驱动
  2. 通过DriverManager类的方法getConnection获得连接对象:指定连接字符串,用户名,密码
  3. 通过调用连接对象的createStatement方法获得发送SQL语句对象。
  4. 准备要执行的SQL语句字符串
  5. 调用发送对象的exceuteUpdate或exceuteQuery执行SQL语句并获得执行结果
  6. 调用ResultSet对象的方法获得数据
  7. 关闭对象释放资源:
  • 开启顺序:Connection --> Statement --> ResultSet
  • 关闭顺序:ResultSet --> Statement --> Connection

示例代码:

public class JDBCDemo01 {
public static void main(String[] args) throws Exception {
// 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 获得连接对象
Connection conn =
DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/day18", "root", "root");
// 获得发送对象
Statement stmt = conn.createStatement();
// 准备SQL语句
String sql = "select * from student;";
// 执⾏行行SQL语句并获得结果集对象
ResultSet rs = stmt.executeQuery(sql);
// 循环获取数据
while (rs.next()){
// 调⽤用结果集对象的方法获得数据
// 根据名获得对应的值
int id = rs.getInt("id");
// 获得姓名
String name = rs.getString("name");
// 获得性别
String gender = rs.getString("gender");
// 获得生日
Date birthday = rs.getDate("birthday");
System.out.println(id+"="+name+"="+gender+"="+birthday);
}
// 关闭资源
rs.close();
stmt.close();
conn.close();
}
}

JDBC事务

Connection接口中与事务有关的方法:

void setAutoCommit(boolean autoCommit)

  • 设置是否自动提交,true自动提交,false不自动提交。
  • 默认是true自动提交事务。
  • 对应:start transaction;

void rollback();

  • 回滚事务。
  • 对应:rollback

void commit();

  • 提交事务。
  • 对应:commit

JDBC事务的使用格式:

  • 在jdbc中的事务都是在try-catch块中使用。
public class JDBCTransactionDemo02 {
public static void main(String[] args) throws Exception{
    Connection con = null;
    try{
        注册驱动
        获取连接
        获取到Statement
        开启事务
        使用statement执行SQL
        提交事务
        关闭资源
  }catch(Exception e){
        回滚事务
        关闭资源

     }
}
}

PreparedStatement

什么是SQL注入:

  • 用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL语句的含义。(通过 ' 或者是 or 语句去改变sql的判断条件)

如何解决SQL注入的问题:

  • 用户输入的内容不能直接跟SQL语句进行拼接操作,需要使用PreparedStatement接口来解决问题。

PreparedStatement接口概述:

  1.  继承Statement。
  2. 表示预编译的SQL语句的对象。

PreparedStatement的作用:

  1. ​​​​​​​解决sql注入的问题。
  2. 提高了程序的可读性。
  3. 一条sql重复执行多次的时候,可以提高效率。
  • ​​​​​​​若用n次Statement则mysql服务器语法检查n次,编译n次,再运行。
  • 若用n次PreparedStatement则mysql服务器语法检查一次,编译一次,再运行。

PreparedStatement和Statement接口的区别:

  1. PreparedStatement有预编译功能,Statement没有预编译功能。
  2. PreparedStatement有缓存功能,Statement没有缓存功能。
  3. PreparedStatement安全性更高,没有SQL注入问题。Statement有SQL注入问题。

 PreparedSatement 执行原理:

PreparedStatement使用步骤:

1)编写sql语句,未知内容使用?占位。

2)获得PreparedStatemen对象。

通过调用连接对象的方法获得,该方法声明如下:

  • PreparedStatement preparedStatement(String sql);

3)设置实际参数。 

​​​​​​​PreparedStatemen接口常用方法:

void setDouble(int parameterIndex, double x)
void setFloat(int parameterIndex, float x)
void setInt(int parameterIndex, int x)
void setLong(int parameterIndex, long x)
void setString(int parameterIndex, String x)

  • parameterIndex代表是第几个参数。占位符的起始索引是从1开始。
  • 给指定位置的占位符?赋值具体的数据。

4)执行参数化SQL语句。

​​​​​​​PreparedStatemen接口常用方法:

boolean excute(String sql)

  • 此方法可以执行任意sql语句,返回boolean值,表示是否返回ResultSet结果集。

int executeUpdate(String sql)

  • 执行增删改操作,返回被影响行数。

ResultSet executeQuery(String sql)

  • 执行查询操作,返回满足条件的结果集。

5)关闭资源。


示例代码:

// 添加数据: 向学⽣生表添加3条记录
@Test
public void insertStudent() throws Exception {
// 获得连接对象
Connection conn = JDBCUtils.getConnection();
// 准备SQL语句句
String sql = "insert into student(name,gender,birthday) values(?,?,?);";
// 获得PreparedStatement对象
PreparedStatement ps = conn.prepareStatement(sql);
// 给占位符?赋值
ps.setString(1, "张强");
ps.setString(2, "男");
ps.setString(3, "1999-10-30");
// 执⾏行行SQL语句句
int row = ps.executeUpdate();
System.out.println("row = " + row);
// 给占位符?赋值
ps.setString(1, "⼩小平");
ps.setString(2, "⼥女女");
ps.setString(3, "1988-06-20");
row = ps.executeUpdate();
System.out.println("row = " + row);
// 给占位符
赋值
ps.setString(1, "凤姐");
ps.setString(2, "⼥女女");
ps.setString(3, "1988-08-20");
row = ps.executeUpdate();
System.out.println("row = " + row);
// 关闭资源
JDBCUtils.close(ps,conn);
}

代理模式

代理模式的作用:

  1. 代理对象可以在调用者和目标对象之间起到中介的作用。
  2. 在不修改真实对象的代码情况下对真实对象的功能进行增强。

代理模式的分类:

  1. 静态代理(几乎不用了)
  2. 动态代理

动态代理

什么是动态代理:

  • 在程序运行过程中动态创建代理对象。

动态代理的作用:

  • 增强一个对象的某个方法。

Proxy类

如何创建代理对象:

  • 通过Proxy类的静态方法创建,该静态方法声明如下:

​​​​​​​public static Object newProxyInstance(ClassLoader loader,
Class[] interfaces,
InvocationHandler h)

  • 动态创建一个对象并且实现你指定的接口。

​​​​​​​1)loader参数:类加载器

  • 固定写法:当前类名.class.getClassLoader()

2)interfaces参数:指定代理对象需要实现的接口数组。

  • 示例:new class[ ] {实现接口的名字.class}

3)h参数:具体的代理操作,InvocationHandler是一个接口,需要传入一个实现了此接口的实现类。

4)返回值:实现指定接口的代理对象。

注意

  • 动态代理对象帮你动态生成的一个对象,实现了你指定的接口的方法,但是实现的时候全都是使用空实现。如果需要实现具体的方法,那么需要处理器的invoke方法去实现。

InvocationHandler接口

InvocationHandler接口常用的方法:

public Object invoke(Object proxy, Method method, Object[] args)​​​​​​​

  • 该方法是用来拦截代理对象方法的调用。
  • 每次通过代理对象调用方法都会被该方法拦截。

​​​​​​​proxy:代理对象本身。一般不要在该方法中,使用proxy调用方法。

method:当前调用的方法。

args:当前传递的参数。(是数组,取出需args[ 0 ]

返回值:真实对象方法的返回值。

处理器的固定步骤:

​​​​​​​1)处理器内部一定要维护一个被代理对象。

2)一定需要在invoke方法内部获取方法名。

  • String methodName = method.getName();

3)判断是否是需要被代理的方法。

  • ​​​​​​​if("方法名".equalsIgnoreCase(methodName))

​​​​​​​3)如果不需要被代理的方法,直接让该方法执行即可,并且使用被代理对象去执行。

  • 直接 method.invoke(被代理对象,args);

4)如果需要被代理的方法,那么就写上自己的业务逻辑即可。

 示例代码1:

public interface Provide {
    public void sellComputer(double money);
    public void repairComputer(double money);
}


public class ComputerFactory implements  Provide {
    @Override
    public void sellComputer(double money) {
        System.out.println("电脑厂商收到了"+money+"钱,电脑发货..");
    }

    @Override
    public void repairComputer(double money) {
        System.out.println("电脑厂商收到了"+money+"钱,维修电脑..");
    }
}


public class ComputerProxy {
    public static void main(String[] args) {
        //得到一个电脑厂商的代理对象。   动态代理对象帮你动态生成的一个对象,实现了你指定的接口的方法,但是实现的时候全部都是使用空实现。如果需要实现
        //具体的方法,那么需要处理器的invoke方法去实现。
        Provide computerProxy = (Provide) Proxy.newProxyInstance(ComputerProxy.class.getClassLoader(),new Class[]{Provide.class},new MyHandler());

        //代理对象只能使接口定义的方法。
        computerProxy.sellComputer(10000);
     ;
    }
}


//处理器
class MyHandler implements InvocationHandler{

    //处理器的内部一定要维护一个被代理对象。
    ComputerFactory factory = new ComputerFactory();

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       //获取当前调用的方法名.
        String methodName = method.getName();

        //需求: 代理卖电脑的功能
        if("sellComputer".equalsIgnoreCase(methodName)){
            //代理你的功能
            double totalPrice = (double) args[0];
            double price = totalPrice*0.7;
            System.out.println("代理商收入了:"+ totalPrice+" 给了电脑厂商:"+ price);
            method.invoke(factory,price);
        }else{  //
            //不需要代理。
            method.invoke(factory,args);
        }
       return null;
    }
}

示例代码2:

public class Demo1 {

    //编写一个代理对象增强add方法,添加之前给我提示,添加成功之后也给我提示.
    public static List getListProxy(){

        List list = new ArrayList();

        //第一步:得到一个代理对象
       List proxyList = (List) Proxy.newProxyInstance(Demo1.class.getClassLoader(), new Class[]{List.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //获取方法名
                String methodName = method.getName();
                if ("add".equalsIgnoreCase(methodName)) {
                    //我需要代理
                    Object ele = args[0];
                    System.out.println("我准备添加"+ele+"了喔!");
                    boolean flag = (boolean) method.invoke(list,ele);
                    if (flag) {
                        System.out.println("添加"+ele+"成功了喔!");
                    }

                    return flag;

                }else{
                    //不需要代理的
                   return method.invoke(list,args);
                }

            }
        });

        return proxyList;
    }


    public static void main(String[] args) {

        List proxyList = getListProxy();
        proxyList.add("aa");
        proxyList.add("bb");
        System.out.println(proxyList);
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值