1 JDBC
1.1 sql注入攻击
SQL注入:用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义。
假设有登录案例SQL语句如下:
SELECT FROM 用户表 WHERE NAME = 用户输入的用户名 AND PASSWORD = 用户输的密码;
此时,当用户输入正确的账号与密码后,查询到了信息则让用户登录。但是当用户输入的账号为XXX 密码为:XXX’ OR ‘a’=’a时,则真正执行的代码变为:
SELECT FROM 用户表 WHERE NAME = ‘XXX’ AND PASSWORD =’ XXX’ OR ’a’=’a’;
此时,上述查询语句时永远可以查询出结果的。那么用户就直接登录成功了,显然我们不希望看到这样的结果,这便是SQL注入问题。
1.2 sql注入攻击的解决方式-预编译
表中有一条数据,用户名叫jack,密码为abc
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Scanner;
import cn.itcast.utils.JDBCUtils;
/*
如何解决sql注入攻击问题。
使用Statement的子接口叫做PreparedStatment接口,对sql语句进行预编译就可以了。
PreparedStatment可以预先把sql语句存储起来,然后高效的执行。
之前获取sql语句执行者对象
Statment st = con.createStatment();
现在要使用PreparedStatment,PreparedStatement要通过连接对象的prepareStatement()方法获取
PreparedStatement pst = con.prepareStement(sql);//需要传递一个sql语句,表示要对这个sql语句进行预编译。
sql语句中动态的部分叫做参数。并且在使用的使用动态的参数要使用问号占位符代替 ?
预编译的好处:
1. 高效。
2. 解决sql注入攻击问题。
*/
public class Demo02JDBC {
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
System.out.println("请您键盘录入用户名");
String username = sc.nextLine();
System.out.println("请您键盘录入密码");
String password = sc.nextLine();
//获取连接
Connection con = JDBCUtils.getConnection();
//对sql语句进行预编译。 也是在获取sql语句执行者对象,因为PreparedStatement是Statement的子接口。
PreparedStatement pst = con.prepareStatement("SELECT * FROM user WHERE username=? AND password=?");
//设置参数。 使用实际的数据替换掉问号占位符
//使用PreparedStatement里面的setObject方法进行替换。
//void setObject(int parameterIndex, Object x)
//参数parameterIndex: 表示要替换的是第几个问号。 从1开始的。
//参数x: 使用什么具体的数据替换掉问号占位符。
pst.setObject(1, username);// 使用jack替换掉第1个问号占位符。
pst.setObject(2, password);// 表示使用abc替换掉第2个问号占位符
//替换完所有的问号占位符之后,才能去执行sql
ResultSet rs = pst.executeQuery();//不能传递sql语句,因为这个sql语句已经预先存储起来了
//处理结果集
if(rs.next()) {
System.out.println("登录成功");
} else {
System.out.println("登录失败");
}
//释放资源
JDBCUtils.close(rs, pst, con);
}
}
2 连接池
Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商需要让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池!
常见的连接池:DBCP、C3P0。
2.1 DBCP连接池
DBCP:Apache推出的Database Connection Pool
import java.sql.Connection;
import java.sql.SQLException;
import org.apache.commons.dbcp.BasicDataSource;
/*
使用连接池改进之前的工具类。
之前使用工具类获取连接,每次获取连接都会创建一个新的连接,这样既耗时又浪费内存。
现在通过连接池获取连接,这样每次获取连接都不会去创建新连接,而是从连接池中获取。
dbcp连接池是由第三方提供的,所以如果我们要用,需要dao入jar包
核心API:
BasicDataSource: 这个类表示连接池。
构造方法
BasicDataSource():使用空参构造创建对象。
使用步骤:
1. 创建一个连接池对象
2. 设置数据库的四大信息(调用连接池的setXXX方法进行设置)。
void setDriverClassName(String driverClassName):设置驱动类名
void setUrl(String url):设置url
void setUsername(String username):设置用户名
void setPassword(String password):设置密码
3. 通过连接池对象去获取连接。
Connection getConnection()
*/
public class DBCPUtils {
private static BasicDataSource dataSource;
static {
//在静态代码块中创建连接池,这样的话连接池只有一个。
//创建一个连接池对象
dataSource = new BasicDataSource();
//设置数据库的四大信息
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mydb");
dataSource.setUsername("root");
dataSource.setPassword("root");
}
/*
* 定义方法,用来获取数据库连接对象
*/
public static Connection getConnection() throws SQLException {
//通过连接池对象去获取连接。
Connection con = dataSource.getConnection();
//把这个连接返回
return con;
}
}
2.2 dbcp读取配置文件的方式
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
/*
通过读取配置文件的方式来决定数据库的四大信息到底是什么,因为这样做会更加的灵活。
DBCP 这个连接池里面可以自动的加载Properties集合中的键值对。不需要我们手动的调用getProperty去获取。
核心类:
BasicDataSourceFactory: 连接池工厂, 这个类生产连接池。
里面的方法可以创建一个连接池对象
static DataSource createDataSource(Properties properties):创建一个连接池。
参数是一个Properties集合,这个方法会自动把Properties集合中的键值对加载进来。
Properties键值对一定要有数据库的四大信息。
步骤:
1. 创建一个properties配置文件,建议把这个配置文件放在src下。
东西放在src下,同时这个东西也会在bin目录下出现,bin目录也叫作【类路径】
2. 编写这个配置文件。 key为连接池的属性名, value是其对应的属性值。
建议参考连接池的setXXX方法。
比如setUsername,是在username成员变量赋值,比如setPassword是在给password成员变量赋值。
3. 创建Properties集合,并且把配置文件中的数据读取到这个Properties集合中。
4. 通过连接池工厂 BasicDataSourceFactory 调用 createDataSource方法传递Properties集合对象, 这个方法会得到一个连接池对象。
5. 通过连接池对象获取连接
*/
public class DBCPUtils {
private static DataSource dataSouce;
static {
try {
//创建Properties集合,并且把配置文件中的数据读取到这个Properties集合中
Properties p = new Properties();
InputStream in = new FileInputStream("src\\db.properties");
// getResourceAsStream会从类路径下面获取东西。 此时类路径是bin目录,所以会从bin目录下面获取东西
//InputStream in = DBCPUtils.class.getClassLoader().getResourceAsStream("db.properties");
p.load(in);
in.close();
//通过连接池工厂获取连接池
dataSouce = BasicDataSourceFactory.createDataSource(p);//会将参数Properties集合中的键值对加载到连接池中。
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/*
* 用来从连接池中获取数据库连接
*/
public static Connection getConnection() throws Exception {
//通过连接池调用getConnection获取连接
Connection con = dataSouce.getConnection();
return con;
}
}
db.properties文件放在src下(为的是以后出现在bin目录下)
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydb
username=root
password=root
2.3 C3P0连接池(重点)
C3P0开源免费的连接池!目前使用它的开源项目有:Spring、Hibernate等。使用第三方工具需要导入jar包,c3p0使用时还需要添加配置文件 c3p0-config.xml
package cn.itcast.c3p0utils;
import java.beans.PropertyVetoException;
import java.sql.Connection;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
/*
提供一个方法,用来获取连接,这个连接要通过c3p0连接池获取到
c3p0连接池也是第三方提供的,所以也需要导入jar包。
c3p0连接池中的核心类
ComboPooledDataSource:这个类表示一个连接池。
使用步骤
1. 创建一个连接池对象。
2. 设置数据库的四大信息
void setDriverClass(String driverClass):设置驱动类名
void setJdbcUrl(String jdbcUrl): 设置url
void setUser(String user):设置用户名
void setPassword(String password):设置密码
3. 通过连接池获取连接。
Connection getConnection():通过连接池获取一个数据库连接对象
改进:通过读取配置文件的方式获取到数据库的四大信息
步骤:
1. 创建一个c3p0-config.xml【名字不能变】
2. 把这个xml文件放入到src下【位置不能变】
3. 编写xml配置文件的内容。(参考c3p0连接池的帮助文档)
*/
public class C3P0Utils {
//创建一个连接池对象。
private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
/*
static {
try {
//设置数据库的四大信息
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
dataSource.setUser("root");
dataSource.setPassword("root");
} catch (PropertyVetoException e) {
e.printStackTrace();
}
}
*/
/*
* 定义方法,用来获取这个连接池对象
*/
public static DataSource getDataSource() {
return dataSource;
}
/*
* 提供一个方法,用来获取连接,这个连接要通过c3p0连接池获取到
*/
public static Connection getConnection() throws Exception {
//通过连接池获取连接
Connection con = dataSource.getConnection();
return con;
}
}
c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<!--
property 元素的name属性,表示c3p0连接池的属性名
property 元素的值, 表示对应的属性值
name我们需要去c3p0连接池中查询对应的成员变量名。 建议通过setXXX方法去查。比如setPassword,就表示内部有一个属性叫做password,这个方法在给password赋值
-->
<!-- 配置驱动类名 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<!-- 配置的url -->
<property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb</property>
<!-- 配置用户名 -->
<property name="user">root</property>
<!-- 密码 -->
<property name="password">root</property>
</default-config>
</c3p0-config>
3 DBUtils
简化JDBC代码开发,本案例我们将采用apache commons组件一个成员:DBUtils。
DBUtils就是JDBC的简化开发工具包。需要使用技术:连接池(获得连接),SQL语句都没有少。
/*
商品类
javabean:
1. 私有属性,提供get/set
2. 提供空参数构造。
3. 实现Serializable 接口, 一般省略
*/
public class Product {
private int pid;
private String pname;
private int price;
private String flag;
@Override
public String toString() {
return "Product [pid=" + pid + ", pname=" + pname + ", price=" + price + ", flag=" + flag + "]";
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public String getFlag() {
return flag;
}
public void setFlag(String flag) {
this.flag = flag;
}
}
3.1 增删改
package cn.itcast.dbutils;
import java.sql.Connection;
import java.sql.SQLException;
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import cn.itcast.c3p0utils.C3P0Utils;
/*
DBUtils是由第三方提供的一个工具包,可以简化我们对jdbc的操作。
使用前必须要到入jar
DBUtils核心API
DbUtils:提供了一些方法,用来处理释放资源相关的操作。
QueryRunner:核心类, 用来执行sql语句。
ResultSetHandler: 接口。 用来决定查询后的结果到底是什么类型的。 也叫作结果集处理方式。
DBUtils执行增删改的操作:
QueryRunner构造方法:
QueryRunner(): 使用空参数构造创建对象。
QueryRunner(DataSource ds): 需要传递一个连接池类型的参数。
QueryRunner用来执行增删改操作的方法:
update(Connection conn, String sql, Object... params):用来执行增删改操作。如果要使用这个方法,那么创建对象的时候一定要使用空参数构造方法创建
参数conn: 表示数据库连接。
参数sql: 表示sql语句。这个sql中可以使用?占位符
参数params: 是一个可变参数。 表示用来替换?占位符的实际参数。
update(String sql, Object... params): 用来执行增删改操作. 不需要传递数据库连接, 但是要求创建QueryRunner对象时,要传递一个连接池。
参数sql: 表示sql语句。这个sql中可以使用?占位符
参数params: 是一个可变参数。 表示用来替换?占位符的实际参数。
*/
public class Demo01DBUtils {
public static void main(String[] args) throws Exception {
//updateMethod();
deleteMethod();
}
/*
* 使用DBUtils执行删除操作
*/
public static void deleteMethod() throws Exception {
//创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//调用update方法,执行sql
String sql = "DELETE FROM product WHERE pid = ?";
int row = qr.update(C3P0Utils.getConnection(), sql, 13);
System.out.println(row);
}
/*
* 使用DBUtils执行修改数据的操作
*/
public static void updateMethod() throws SQLException {
//创建QueryRunner对象
QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
//调用update方法,执行sql语句
String sql = "UPDATE product SET pname=?, price=?, flag=? WHERE pid=?";
Object[] params = {"臭豆腐", 10, "1", 13};
int row = qr.update(sql, params);
System.out.println("row:" + row);
}
/*
* 用来执行添加操作
*/
public static void addMethod() throws Exception {
//创建一个QueryRunner对象
QueryRunner qr = new QueryRunner();
//调用update方法执行sql
//update(Connection conn, String sql, Object... params)
Connection con = C3P0Utils.getConnection();
String sql = "INSERT INTO product (pname, price, flag) VALUES (?,?,?)";
Object[] params = {"土豆牛肉盖饭", 15, "1"};//表示用来替换问号占位符的实际参数
int row = qr.update(con, sql, params);//返回值为受影响的行数
System.out.println("row:" + row);
//c3p0会自动回收空闲连接
//DbUtils.close(con);//真正并不是真的把连接关了,而是又放回了连接池
}
}
3.2 查
ResultSetHandler结果集处理类
ArrayHandler | 将结果集中的第一条记录封装到一个Object[]数组中,数组中的每一个元素就是这条记录中的每一个字段的值 |
---|---|
ArrayListHandler | 将结果集中的每一条记录都封装到一个Object[]数组中,将这些数组在封装到List集合中。 |
BeanHandler | 将结果集中第一条记录封装到一个指定的javaBean中。 |
BeanListHandler | 将结果集中每一条记录封装到指定的javaBean中,将这些javaBean在封装到List集合中 |
ColumnListHandler | 将结果集中指定的列的字段值,封装到一个List集合中 |
KeyedHandler | 将结果集中每一条记录封装到Map |
MapHandler | 将结果集中第一条记录封装到了Map |
MapListHandler | 将结果集中每一条记录封装到了Map |
ScalarHandler | 它是用于单个数据。例如select count(*) from 表操作。 |
红色为重点掌握,其他了解。
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ArrayHandler;
import org.apache.commons.dbutils.handlers.ArrayListHandler;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ColumnListHandler;
import org.apache.commons.dbutils.handlers.KeyedHandler;
import org.apache.commons.dbutils.handlers.MapHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import cn.itcast.bean.Product;
import cn.itcast.c3p0utils.C3P0Utils;
/*
使用DBUtils执行查询操作。
如果执行查询就不能再调用update方法,需要调用query方法。
QueryRunner构造方法:
QueryRunner(): 使用空参数构造创建对象。
QueryRunner(DataSource ds): 需要传递一个连接池类型的参数。
DBUtils用来执行查询的方法
query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params)。执行查询,需要传递连接
参数conn: 表示数据库连接
参数sql: sql语句
参数rsh:是一个接口类型的参数。 需要传递实现类对象,根据传递的实现类的不同得到的查询结果也是不同的。 一般叫做结果集处理方式。
参数params:替换问号占位符的实际参数。
query(String sql, ResultSetHandler<T> rsh, Object... params)。 执行查询,不需要传递连接
参数sql: sql语句
参数rsh:是一个接口类型的参数。 需要传递实现类对象,根据传递的实现类的不同得到的查询结果也是不同的。 一般叫做结果集处理方式。
参数params:替换问号占位符的实际参数。
查询的结果集处理方式,一共有9种,这9种全部都是ResultSetHandler的实现类。
ArrayHandler结果集:会将查询后的第一行数据封装到一个Object数组中。 返回值就是Object[]
ArrayListHandler结果集: 会将查询后的每一行数据都封装到各自的Object数组,然后再把这些Object数组放入到一个list集合。 返回值List<Object[]>
(重要)BeanHandler结果集:会将查询的第一行数据封装到一个javabean对象中。
(重要)BeanListHandler结果集: 将查询到的每一行数据都封装到各自的javabean对象中, 把这些javabean对象放入到一个List集合。返回值List<javabean的类型>
MapHandler结果集: 将查询的第一行数据封装到一个Map集合中。 key是列名。 value是对应该列的值
MapListHandler结果集: 将查询后的每一行数据都封装各自的Map集合,再把这些Map集合放入到一个List集合。 返回值: List<Map<String, Object>>
ColumnListHandler结果集: 将查询后指定列的数据封装到一个List集合。
(重要)ScalarHandler结果集: 对于查询后只有一个结果的情况,可以使用这个结果集。 比如 select count(*) from product;
KeyedHandler结果集: 得到结果是一个Map集合,这个Map集合中的key是指定列的数据。
value也是一个Map集合. key是每列的列名,value是对应的值。
*/
public class Demo02DBUtils {
public static void main(String[] args) throws Exception {
arrayHandler();
}
public static void keyedHandler() throws Exception {
//创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//调用query 方法执行查询
String sql = "SELECT * FROM product";
Map<Object, Map<String, Object>> map = qr.query(C3P0Utils.getConnection(), sql, new KeyedHandler<>("pname"));
//遍历这个Map集合
for(Object key : map.keySet()) {
System.out.println(key + "------------" + map.get(key));
}
}
//ScalarHandler结果集: 对于查询后只有一个结果的情况,可以使用这个结果集。 比如 select count(*) from product;
public static void scalarHandler() throws Exception {
//创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//调用query 方法执行查询
String sql = "SELECT COUNT(*) FROM product";
Object num = qr.query(C3P0Utils.getConnection(), sql, new ScalarHandler<>());
System.out.println(num);
}
//ColumnListHandler结果集: 将查询后指定列的数据封装到一个List集合。
public static void columnListHandler() throws Exception {
//创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//调用query 方法执行查询
String sql = "SELECT * FROM product";
/*
* ColumnListHandler有三个构造方法。
* 空参数表示获取的是查询结果第一列的数据
* 也可以在参数位置传递列名或者索引
*/
List<Object> list = qr.query(C3P0Utils.getConnection(), sql, new ColumnListHandler<>("pname"));
for(Object obj : list) {
System.out.println(obj);
}
}
//MapListHandler结果集: 将查询后的每一行数据都封装各自的Map集合,再把这些Map集合放入到一个List集合。 返回值: List<Map<String, Object>>
public static void mapListHandler() throws Exception {
//创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//调用query 方法执行查询
String sql = "SELECT * FROM product";
List<Map<String, Object>> list = qr.query(C3P0Utils.getConnection(), sql, new MapListHandler());
//遍历List集合
for(Map<String, Object> map : list) {
System.out.println(map);
}
}
//MapHandler结果集: 将查询的第一行数据封装到一个Map集合中。 key是列名。 value是对应该列的值
public static void mapHandler() throws Exception {
//创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//调用query 方法执行查询
String sql = "SELECT * FROM product";
Map<String, Object> map = qr.query(C3P0Utils.getConnection(), sql, new MapHandler());
//打印
System.out.println(map);
}
//BeanListHandler结果集: 将查询到的每一行数据都封装到各自的javabean对象中, 把这些javabean对象放入到一个List集合。返回值List<javabean>的类型>
public static void beanListHandler() throws Exception {
//创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//调用query 方法执行查询
String sql = "SELECT * FROM product";
List<Product> list = qr.query(C3P0Utils.getConnection(), sql, new BeanListHandler<>(Product.class));
//遍历集合
for(Product p : list) {
System.out.println(p);
}
}
//BeanHandler结果集:会将查询的第一行数据封装到一个javabean对象中。
public static void beanHandler() throws Exception {
//创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//调用query 方法执行查询
String sql = "SELECT * FROM product";
//封装到什么javabean对象,在BeanHandler构造方法位置就传递哪个类的.class
//new BeanHandler<>(类.class) 传递哪个类的class,就表示封装成什么javabean对象
//Product 这个javabean对象中的字段一定要和数据库中的列名一致。
Product p = qr.query(C3P0Utils.getConnection(), sql, new BeanHandler<>(Product.class));
System.out.println(p);
}
//ArrayListHandler结果集: 会将查询后的每一行数据都封装到各自的Object数组,然后再把这些Object数组放入到一个list集合。 返回值List<Object[]>
public static void arrayListHandler() throws Exception {
//创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//调用query 方法执行查询
String sql = "SELECT * FROM product";
List<Object[]> list = qr.query(C3P0Utils.getConnection(), sql, new ArrayListHandler());
//遍历集合
for(Object[] objs : list) {
System.out.println(Arrays.toString(objs));
}
}
//ArrayHandler结果集:会将查询后的第一行数据封装到一个Object数组中。
public static void arrayHandler() throws Exception {
//创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//调用query方法,去执行查询。
String sql = "SELECT * FROM product";
Object[] objs = qr.query(C3P0Utils.getConnection(), sql, new ArrayHandler());
System.out.println(Arrays.toString(objs));
}
}