1. 预编译对象PreparedStatement
1.1 SQL注入问题
- 输入的内容作为SQL语句语法的一部分,改变了原有SQL真正的意义
- 需要用预编译对象解决这个问题
1.2 PreparedStatement介绍
- PreparedStatement是一个接口,其超级接口为Statement和Wrapper,子接口为CallableStatement;
1.3 PreparedStatement的执行原理
数据库需要执行编译后的SQL语句;
Statement和PreparedStatement的区别:
- 对于相同的SQL语句,Statement对象每次执行都会先将该语句发送给数据库编译,数据库再执行。每次都要编译,耗时较大。执行次数越多,效率越低;
- 而PreparedStatement对象先将相同的SQL语句发给数据库预编译,PreparedStatement对象会引用预编译后的结果(相当于数据库只预编译一次,后面只需根据传入的参数执行语句,减少编译次数,提高效率)
1.4 PreparedStatement的好处
- 减少SQL编译次数,提高效率(前提:同一条语句多次重复执行);
- 安全性更高,没有SQL注入的隐患;
- 提高程序的可读性。
1.5 PreparedStatement实现增删改查
1.5.1 API介绍
1.5.1.1 获取PreparedStatement对象
PreparedStatement preparestatement(String sql)
将SQL语句发送给数据库预编译,PreparedStatement对象会引用预编译后的结果。使用Connection对象调用该方法获取。
1.5.1.2 PreparedStatement常用方法
- void setInt(int parameterIndex, int x);
将指定参数设置为给定Java int 值
- void setLong(int parameterIndex, long x);
将指定参数设置为给定Java long 值
- void setFloat(int parameterIndex, float x);
将指定参数设置为给定Java float 值
- void setDouble(int parameterIndex, double x);
将指定参数设置为给定Java double 值
- void setObject(int parameterIndex, object x);
使用给定对象设置指定参数的值
- void setString(int parameterIndex, String x);
将指定参数设置为给定Java String 值
ResultSet executeQuery();
在此PreparedStatement对象中执行SQL查询,并返回该查询生成的ResultSet对象
int executeUpdate();
在此 PreparedStatement 对象中执行 SQL 语句,该语句必须是一个 SQL 数据操作语言(DML)语句,比如 INSERT、UPDATE 或 DELETE 语句;或者是无返回内容的 SQL 语句,比如 DDL语句。
1.5.2 示例代码
- PreparedStatement使用步骤
- 编写SQL语句,未知内容使用
?
占位; - 获得PreparedStatement对象;
- 设置实际参数;
- 执行参数化SQL语句;
- 关闭资源。
- 编写SQL语句,未知内容使用
/*
下面是用了JDBC里面定义的工具类
*/
public class Demo01 {
/*插入数据*/
public void add() throws SQLException {
//获取连接
Connection connection = JDBCUtils.getConnection();
//准备sql语句,得到preparedstatement
String sql = "insert into student(name,age) values(?,?)";
PreparedStatement pst = connection.prepareStatement(sql);
//设置参数
pst.setString(1,"大黄");
pst.setInt(2,18);
//执行sql
pst.executeUpdate();
//关闭资源
JDBCUtils.close(null,pst,connection);
}
/*删除数据 */
public void delete() throws SQLException {
//获取连接
Connection connection = JDBCUtils.getConnection();
//准备sql语句,得到preparedstatement
String sql = "delete from student where id=?";
PreparedStatement pst = connection.prepareStatement(sql);
//设置参数
pst.setInt(1,1);
//执行sql
pst.executeUpdate();
//关闭资源
JDBCUtils.close(null,pst,connection);
}
/*修改数据*/
public void update() throws SQLException {
//获取连接
Connection connection = JDBCUtils.getConnection();
//准备sql语句,得到preparedstatement
String sql = "update student set name = ? where id = ?";
PreparedStatement pst = connection.prepareStatement(sql);
//设置参数
pst.setString(1,"大狗");
pst.setInt(2,2);
//执行sql
pst.executeUpdate();
//关闭资源
JDBCUtils.close(null,pst,connection);
}
/*修改数据*/
public void query() throws SQLException {
//获取连接
Connection connection = JDBCUtils.getConnection();
//准备sql语句,得到preparedstatement
String sql = "select * from student";
PreparedStatement pst = connection.prepareStatement(sql);
//执行sql
ResultSet rs = pst.executeQuery();
while(rs.next()){
System.out.println("编号:"+ rs.getInt("id")+" 姓名:"+ rs.getString("name")+" 年龄:"+ rs.getInt("age"));
}
/*关闭资源*/
JDBCUtils.close(null,pst,connection);
}
}
2. 自定义连接池和动态代理
- 连接池是创建和管理数据库连接的缓冲池技术。连接池中保存了一些数据库可重复使用的连接。
2.1 连接池解决数据库重复创建连接的问题
- 不使用连接池的情况:
- 每次访问数据库执行SQL语句都需要创建数据库连接,,而创建连接需要消耗较多资源,创建时间较长,导致连接对象使用率低
2.1.1 连接池解决问题的原理
- 程序一开始创建一定数量的连接,放在容器(连接池)中;
- 使用时直接取出已经创建好的连接对象;
- 关闭是不真正关闭连接,而是将连接对象再次放回连接池
2.2 自定义连接池
连接池的公共接口:
javax.sql.DataSource
自定义步骤如下
- 定义一个
javax.sql.DataSource
接口的实现类 - 重写接口的抽象方法
- 定义连接池相关参数
- 连接池必要因素:初始化连接个数、最大的连接个数、当前已创建的连接个数
- 创建容器保存连接
- 创建集合用以存储Connection
- 提供创建新连接的方法
- 提供连接池的构造方法
- 提供获取连接方法(看示例代码)
- 提供关闭连接方法(看示例代码)
- 定义一个
/*
自定义连接池
*/
import studynotes.Notes00_预编译对象.JdbcUtils;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.LinkedList;
import java.util.logging.Logger;
//1. 定义一个javax.sql.DataSource接口的实现类
public class MyPool implements DataSource {
//3. 定义连接池相关参数
// - 连接池必要因素:初始化连接个数、最大的连接个数、当前已创建的连接个数
private int initCount = 3;
private int maxCount = 10;
private int curCount = 0;//创建时默认为零
//4. 创建容器保存连接
// - 创建集合用以存储Connection==>连接池
private LinkedList<Connection> list = new LinkedList<>();
//5.提供创建新连接的方法
public Connection createConnection() {
//可使用工具类创建连接
Connection connection = JdbcUtils.getConnection();
curCount++;
return connection;
}
//6.提供连接池的构造方法
public MyPool() {
//一创建连接池就按照初始化连接个数创建连接
for (int i = 0; i < initCount; i++) {
Connection connection = createConnection();
//添加到连接池中
list.add(connection);
}
}
//2. 重写接口的抽象方法
//7. 提供获取连接方法
@Override
public Connection getConnection() throws SQLException {
/*分为三种情况*/
//情况1:连接池中还有连接,直接获取
if (list.size() > 0) {
return list.removeFirst(); //删除集合元素的同时返回值为被删除对象,实现从连接池取出连接的操作
}
//情况2:连接池中没有连接,但是连接的总个数没有超过最大值
//注意:情况2不能和情况1的位置调换,否则会出现初始化的3个还没利用就开始创建新的连接
if (curCount < maxCount) {
return createConnection();
}
//情况三:连接总个数达到最大值,且连接池没有连接,则抛出异常
throw new RuntimeException("当前访问人数过多,请稍后重新连接!");
}
//8.提供关闭连接方法,把链接还回给连接池
public void close(Connection connection){
list.add(connection);
}
/*其余的没有用到,直接空实现*/
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
}
注意:根据编程习惯,一般是习惯用资源直接调用方法。此时可使用动态代理增强方法。
2.3 代理模式(重要)
2.3.1 代理模式的作用
- 代理对象可以在调用者和目标对象之间其到中介的作用。代理对象可对目标对象的某个功能进行增强。
- 优点1:动态代理可以在不知道类名的基础下直接增强某个功能,而继承需要知道父类名才能重载增强;
- 优点2:实现客户与被代理类间的解耦
- 缺点:动态代理需要接口,如果一个类不实现任何接口,则动态代理无法增强
2.3.2 代理模式涉及到4个要素
- 调用者:使用数据库的用户
- 代理对象
- 目标对象:实现接口中的方法
- 抽象对象:代理对象和目标对象共有的接口
2.3.3 动态代理
- 程序运行过程中,动态创建出代理对象
2.3.3.1 动态代理相应的API
Proxy类
- static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h); 作用:生成实现指定接口的代理对象 需要用Proxy类对象调用
其中:
loader参数
:目标对象的类加载器(格式:当前模块下的任意类的名字.class.getClassLoader()
)
interfaces
:代理对象所实现的接口数组(接口名.class的数组)h
: 具体的代理操作(处理器),InvocationHandler是一个接口,需要传入一个实现了此接口的实现类。
返回值
:实现指定接口的代理对象。如何理解处理器的作用
动态代理对象实现接口的时候时采用空实现(重写方法中没有方法体),而是通过处理器将接口中的方法分类实现(增强被代理的方法,原汁原味地实现不被代理的方法)
InvocationHandler接口
- Object invoke(Object proxy, Method method, Object[] args); 作用:在这个方法中实现对真实方法的增强
其中:
proxy
:当前代理对象。
method
: 代理对象当前调用的方法。
args
:代理对象当前调用方法时传递的参数。返回值
:是真实对象方法的返回值。图解:
![invoke方法参数的说明](E:\Java study\每日笔记\Java笔记汇总\notesPic\invoke方法参数的说明.jpg)
2.3.3.2 动态代理的使用步骤及原则
动态代理的使用步骤
- 直接创建真实对象;
- 通过Proxy类创建代理对象;
- 调用代理方法;
- 在InvocationHandler的invoke分别进行处理被代理方法和非代理方法。
原则(固定套路)
- 处理器内部一定要维护一个被代理对象(保证处理器内部的方法均由被代理对象执行【因为实际上执行方法的是被代理对象而不是代理对象!!】)
- 处理器内部一定需要在invoke方法内获取当前调用的方法名
- 如果不需要被代理的方法,则使用被代理对象直接执行该方法(这些方法不被代理不需要增强功能,按原功能执行即可,且必须将方法的返回值返回去)
- 如果是需要被代理的方法,则根据自己的业务逻辑(业务需求)改写方法即可
2.3.3.3 动态代理的示例代码
需求:实现黄牛与火车站的代理关系,黄牛赚差价
public class YellowCow {
public static void main(String[] args) {
//获取动态代理对象,因为返回的是一个代理对象(该对象是一个接口的实现类对象),可以直接强转
Fuction yellowCow = (Fuction) Proxy.newProxyInstance(YellowCow.class.getClassLoader(),
new Class[]{Fuction.class}, new MyHandler());
//代理对象只能使用接口定义的方法
yellowCow.sellTicket(1000);
}
}
class MyHandler implements InvocationHandler {
/*处理器的内部一定要维护一个被代理对象。*/
Trainstation trainstation = new Trainstation();
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取当前调用的方法名
String methodName = method.getName();
//判断是否为代理的功能
if ("sellTicket".equalsIgnoreCase(methodName)) {
//根据业务逻辑编写代理的功能
Double totalPrice = (Double) args[0];//获取传入参数的第一个参数,因为已知类型为double,所以直接强转
double payPrice = totalPrice*0.5;
System.out.println("代理商收入了:"+ totalPrice+" 给了电脑厂商:"+ payPrice);
//实际执行方法的仍是被代理对象,即火车站!!!
/*invoke方法的第一个参数为方法的执行者,第二个参数是执行方法时需要传入的参数*/
return method.invoke(trainstation,payPrice);
} else {
//接口中不需要被代理的功能,直接执行即可
return method.invoke(trainstation,args);
}
}
}
2.4 动态代理解决close问题(增强close功能)
- 那么之后就可以直接用资源(连接)调用close方法,而不需要
MyPool2.close();
这样不清晰的调用
/*
使用动态代理解决close问题(增强close功能)
*/
import studynotes.Notes00_预编译对象.JdbcUtils;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.LinkedList;
import java.util.logging.Logger;
//1. 定义一个javax.sql.DataSource接口的实现类
public class MyPool2 implements DataSource {
//3. 定义连接池相关参数
private int initCount = 3;
private int maxCount = 10;
private int curCount = 0;
//4. 创建容器保存连接
private LinkedList<Connection> list = new LinkedList<>();
//5.提供创建新连接的方法
public Connection createConnection() {
//维护一个被代理对象(方法实际执行者)
Connection connection = JdbcUtils.getConnection();
//产生一个代理对象,该代理对象仍是被代理对象类型的
Connection proxyConnection = (Connection) Proxy.newProxyInstance(MyPool2.class.getClassLoader(),
new Class[]{Connection.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获得方法名
String methodName = method.getName();
//如果是需要增强的方法
if ("close".equalsIgnoreCase(methodName)) {
//需要被代理的方法,调用close方法是将当前连接放回到连接池中。理解:外面调用close方法时用资源直接.close的
list.add((Connection)proxy);
return null;
} else {
//如果不是代理的方法,直接执行即可
return method.invoke(connection,args);
}
}
});
/*上述的可以用lambda改写*/
curCount++;
//将代理对象返回去
return proxyConnection;
}
//6.提供连接池的构造方法
public MyPool2() {
for (int i = 0; i < initCount; i++) {
Connection connection = createConnection();
list.add(connection);
}
}
//2. 重写接口的抽象方法
//7. 提供获取连接方法
@Override
public Connection getConnection() throws SQLException {
/*分为三种情况*/
//情况1:连接池中还有连接,直接获取
if (list.size() > 0) {
return list.removeFirst();
}
//情况2:连接池中没有连接,但是连接的总个数没有超过最大值
if (curCount < maxCount) {
return createConnection();
}
//情况三:连接总个数达到最大值,且连接池没有连接,则抛出异常
throw new RuntimeException("当前访问人数过多,请稍后重新连接!");
}
//8.提供关闭连接方法,把链接还回给连接池
public void close(Connection connection){
list.add(connection);
}
/*其余的没有用到,直接空实现*/
/*此处省略没用到的抽象方法*/
}
3. C3P0连接池
3.1 概念及常用配置参数
开源的连接池。jar包
c3p0-0.9.5.2.jar
常用的配置参数:
参数 | 说明 |
---|---|
initialPoolSize | 初始连接数 |
maxPoolSize | 最大连接数 |
checkoutTimeout | 最大等待时机 |
maxIdleTime | 最大空闲回收时间 |
3.2 C3P0连接池的使用步骤
- 导入jar包
c3p0-0.9.5.2.jar
和mchange-commons-java-0.2.12.jar
- 编写c3p0-config.xml 配置文件,配置对应参数
- 将配置文件放在src目录下(注意:不能改文件位置,不能更改文件名)
- 创建连接池对象ComboPooledDataSource ,使用默认配置或命名配置
- 从连接池中获取连接对象
- 使用连接对象操作数据库
- 关闭资源
3.3 C3P0连接池常用方法
- public ComboPooledDataSource()
无参构造使用默认配置(使用xml中default‐config标签中对应的参数)
- public ComboPooledDataSource(String configName)
有参构造使用命名配置(configName:xml中配置的名称,使用xml中named‐config标签中对应的参数)
- public Connection getConnection() throws SQLException
从连接池中取出一个连接
3.4 C3P0连接池的注意事项及好处
- 注意事项:C3P0配置文件名称必须为
c3p0-config.xml
- 好处:
- C3P0命名配置可以有多个,可根据连接池名字连接不同的数据库、不同的连接池、和不同厂商的数据库
3.5 示例代码
/*
使用前提:根据使用环境对配置文件的内容进行调整完毕
*/
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class Notes03 {
/*
配置文件信息处理完毕后,可以直接创建C3P0连接池
*/
public static void main(String[] args) throws SQLException {
/*
无参时使用默认的连接池。由于C3P0可以配置多个连接池,可以在参数中传入其他连接池的名字,达到转换连接池的效果
*/
ComboPooledDataSource dataSource = new ComboPooledDataSource();
Connection connection = dataSource.getConnection();
System.out.println("已连接:"+ connection);
connection.close();
//有参
ComboPooledDataSource dataSource1 = new ComboPooledDataSource("anotherc3p0");
Connection connection1 = dataSource.getConnection();
System.out.println("已连接:"+ connection1);
connection1.close();
}
}
4. DRUID连接池
- 阿里巴巴开发的数据库连接池
4.1 常用API
创建连接池
- public static DataSource createDataSource(Properties properties) 创建一个连接池,连接池的参数使用properties中的数据
DRUID连接池的配置文件建议放在src目录下方便加载。使用前对配置文件的信息进行修改。
4.2 DRUID使用
配置文件Properties的加载:利用类文件路径获取输入流,然后再load
InputStream is = 当前类名.class.getResourceAsStream("类文件路径");
其中类文件路径建议为/druid.properties,
/
代表src目录获取连接池:使用工具类DruidDataSourceFactory类的
*createDataSource*(properties);
方法
其余使用同普通连接池
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;
public class Notes04 {
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
InputStream is = Notes04.class.getResourceAsStream("/druid.properties");
properties.load(is);
//得到一个连接池
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
/*以下操作同普通连接池*/
Connection connection = dataSource.getConnection();
System.out.println("已连接:"+ connection);
connection.close();
}
}
4.3 Jdbc工具类
- 声明静态数据源成员变量
- 创建连接池对象
- 定义公有的得到数据源的方法
- 定义得到连接对象的方法
- 定义关闭资源的方法
示例代码:
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JDBCUtils {
private static DataSource dataSource ;
/* 配置文件的加载只需加载一次,使用静态代码块*/
static{
try {
Properties properties =new Properties();
InputStream is = JDBCUtils.class.getResourceAsStream("/druid.properties");
properties.load(is);
//创建druid的连接池
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
//获取连接池
public static DataSource getDataSource(){
return dataSource;
}
//获取连接
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
//关闭资源
public static void close(ResultSet rs , Statement st , Connection conn){
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(st!=null){
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}