JDBC
客户端操作MYSQL数据库的方式:
- 通过命名行登录数据库。
- 通过图形化软件登录数据库,比如Sqlyog,Navicate...
- 通过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获取连接步骤
- 导入驱动jar包
- 注册驱动
- 获取连接
加载数据库的驱动类
方式一:(推荐)
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的原理:
- ResultSet内部有一个指针,刚开始记录开始位置。
- 调用next方法,ResultSet内部指针会移动到下一行数据。
- 我们可以通过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开发步骤小结:
- 通过Class.forName("驱动类全名")注册数据库驱动
- 通过DriverManager类的方法getConnection获得连接对象:指定连接字符串,用户名,密码
- 通过调用连接对象的createStatement方法获得发送SQL语句对象。
- 准备要执行的SQL语句字符串
- 调用发送对象的exceuteUpdate或exceuteQuery执行SQL语句并获得执行结果
- 调用ResultSet对象的方法获得数据
- 关闭对象释放资源:
- 开启顺序: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接口概述:
- 继承Statement。
- 表示预编译的SQL语句的对象。
PreparedStatement的作用:
- 解决sql注入的问题。
- 提高了程序的可读性。
- 一条sql重复执行多次的时候,可以提高效率。
- 若用n次Statement则mysql服务器语法检查n次,编译n次,再运行。
- 若用n次PreparedStatement则mysql服务器语法检查一次,编译一次,再运行。
PreparedStatement和Statement接口的区别:
- PreparedStatement有预编译功能,Statement没有预编译功能。
- PreparedStatement有缓存功能,Statement没有缓存功能。
- 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);
}
代理模式
代理模式的作用:
- 代理对象可以在调用者和目标对象之间起到中介的作用。
- 在不修改真实对象的代码情况下对真实对象的功能进行增强。
代理模式的分类:
- 静态代理(几乎不用了)
- 动态代理
动态代理
什么是动态代理:
- 在程序运行过程中动态创建代理对象。
动态代理的作用:
- 增强一个对象的某个方法。
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);
}
}