常用API
大约 5 分钟数据库技术JDBC
在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:
- Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
- PreparedStatement:语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
- CallableStatement:用于执行 SQL 存储过程。
Statement更新
@Test
public void test5() throws SQLException, ClassNotFoundException, IOException {
// 1.定义要素
Properties properties = new Properties();
properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("jdbc.properties"));
String driverName = properties.getProperty("driverName");
String url = properties.getProperty("url");
String username = properties.getProperty("username");
String password = properties.getProperty("password");
// 2.加载驱动,实例化驱动。并注册驱动
// 仅仅需要jvm加载一下
Class.forName(driverName);
// 4.获取连接
Connection connection = DriverManager.getConnection(url, username, password);
// 5.创建
Statement statement = connection.createStatement();
String sql = "insert into person (name, age) values ('ww', 40)";
int affectRows = statement.executeUpdate(sql);
System.out.println(affectRows);
}
1
Process finished with exit code 0
Statement查询
Java与SQL数据类型转换表
Java类型 | SQL类型 |
---|---|
boolean | BIT |
byte | TINYINT |
short | SMALLINT |
int | INTEGER |
long | BIGINT |
String | CHAR,VARCHAR... |
byte | BINARY |
java.sql.Date(日期,没有时间) | DATE |
java.sql.Time(只有时间) | TIME |
java.sql.Timestamp | TIMESTAMP |
@Test
public void test5() throws SQLException, ClassNotFoundException, IOException {
// 1.定义要素
Properties properties = new Properties();
properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("jdbc.properties"));
String driverName = properties.getProperty("driverName");
String url = properties.getProperty("url");
String username = properties.getProperty("username");
String password = properties.getProperty("password");
// 2.加载驱动,实例化驱动。并注册驱动
// 仅仅需要jvm加载一下
Class.forName(driverName);
// 4.获取连接
Connection connection = DriverManager.getConnection(url, username, password);
// 5.创建
Statement statement = connection.createStatement();
String sql = "select id, name, age from person";
ResultSet resultSet = statement.executeQuery(sql);
List<User> userList = new ArrayList<>();
while (resultSet.next()){
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
int age = resultSet.getInt(3);
User user = new User();
user.setId(id);
user.setName(name);
user.setAge(age);
userList.add(user);
}
System.out.println(userList);
}
结果:
[User{id=1, name='zs', age=26}, User{id=5, name='ww', age=40}]
Process finished with exit code 0
SQL注入
简单模拟一下登录:输入正确的用户名,根据用户名查询信息。正常来说,输入的用户名数据库中没有就查不出信息。
public static void main(String[] args) throws SQLException, IOException, ClassNotFoundException {
// 输入用户名和密码
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名: ");
String username = scanner.nextLine();
// 登录
String sql = "select id, name, age from person where name = '" + username + "'" ;
Connection connection = DbUtils.getConnection();
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()){
User user = new User();
user.setId(resultSet.getInt(1));
user.setName(resultSet.getString(2));
user.setAge(resultSet.getInt(3));
System.out.println(user);
}
}
请输入用户名:
zs
User{id=1, name='zs', age=26}
Process finished with exit code 0
随便输入:
请输入用户名:
122
Process finished with exit code 0
但是如果我输入下面的,就会查询出数据库的全部数据。数据就泄露了。
xxx' or 1 = '1
请输入用户名:
xxx' or 1 = '1
sql: select id, name, age from person where name = 'xxx' or 1 = '1'
User{id=1, name='zs', age=26}
User{id=5, name='ww', age=40}
Process finished with exit code 0
这时,where后面拼接了一个永远为true的条件,就会查询整张表。相当于or之前的条件全部失效了。
如果是进行用户名和密码的校验,将会出现用户名和密码都是错的情况,但是进入了系统,这就是非常不安全的了。数据就全部泄露了。这就是SQL注入。SQL注入有很多办法,这是最简单的一种,我们要避免SQL注入。Statement是无法避免的。
PreparedStatement的使用
mysql预编译
- 通常我们发送一条SQL语句给MySQL服务器时,MySQL服务器每次都需要对这条SQL语句进行校验、解析等操作。
- 但是有很多情况下,我们的【一条SQL语句】可能需要反复的执行,每次执行可能仅仅是传递的参数不同而已,类似于这样的SQL语句如果每次都需要进行校验、解析等操作,未免太过于浪费性能了,因此产生了SQL语句的预编译。
- 所谓【预编译】就是将一些灵活的参数值以占位符
?
的形式给代替掉,我们把参数值给抽取出来,把SQL语句进行模板化。让MySQL服务器执行相同的SQL语句时,不需要在校验、解析SQL语句上面花费重复的时间。
-- 预编译sql
PREPARE st from 'SELECT * FROM person WHERE id = ?';
SET @id = 1;
EXECUTE st USING @id;
SET @id = 5;
EXECUTE st USING @id;
PreparedStatement
默认使用PreparedStatement是【不能执行预编译】的,这需要在url中给出useServerPrepStmts=true
参数(MySQL Server 4.1之前的版本是不支持预编译的,而Connector/J在5.0.5以后的版本,默认是没有开启预编译功能的。
useServerPrepStmts=true&cachePrepStmts=true
为了查看效果,我们打开mysql的通用查询日志:
show VARIABLES like '%general_log%';
SET GLOBAL general_log=1;
ublic static void main(String[] args) throws SQLException, IOException, ClassNotFoundException {
// 输入用户名和密码
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名: ");
String username = scanner.nextLine();
// 登录
String sql = "select id, name, age from person where name = ?" ;
System.out.println("sql: " + sql);
// xxx' or 1 = '1
Connection connection = DbUtils.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, username);
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()){
User user = new User();
user.setId(resultSet.getInt(1));
user.setName(resultSet.getString(2));
user.setAge(resultSet.getInt(3));
System.out.println(user);
}
}
输入:
请输入用户名:
xxx' or 1 = '1
sql: select id, name, age from person where name = ?
Process finished with exit code 0
MySQL服务端没有开启预编译:
2022-12-02T07:00:21.021462Z 77 Query SET character_set_results = NULL
2022-12-02T07:00:21.021769Z 77 Query SET autocommit=1
2022-12-02T07:00:21.036760Z 77 Query select id, name, age from person where name = 'xxx'' or 1 = ''1'
开启预编译:
2022-12-02T07:04:09.662687Z 78 Query SET character_set_results = NULL
2022-12-02T07:04:09.663076Z 78 Query SET autocommit=1
2022-12-02T07:04:09.680767Z 78 Prepare select id, name, age from person where name = ?
2022-12-02T07:04:09.685223Z 78 Execute select id, name, age from person where name = 'xxx\' or 1 = \'1'