第一部XML(XML相关API)
XML技术 (2天)
--》 写XML
--》 读XML
第二部分:JavaWeb开发
Servlet / JSP (Servlet相关API)
相关接口:
HttpServletRequest/response/ServletContext/HttpSession…….
第三部分:数据库
MySQL数据库
第四部分:JDBC(JDBC相关API)
JDBC技术: java数据库连接技术!
接口:
Connection: 连接对象
Statement: 执行命令对象: 把SQL语句发送到数据库执行
ResultSet: (在线式)结果集接口, 必须要保持与数据库的连接!
我们在学某一个技术点时,大都是学习该技术点的相关API。所以,对于java来
说,与其说是面向对象开发,不如说面向接口开发。
+++ JDBC核心的API:
驱动类
驱动管理器类
连接对象类
连接对象类可以创建不同的对象,来执行不同类型的sql。
statement对象:执行静态sql
preparedStatemnt对象:执行动态sql
callableStatent对象:执行存储过程。
+++
java.sql.* 和 javax.sql.*
|- Driver接口: 表示java驱动程序接口。所有的具体的数据库厂商要来实现此接口。
|- connect(url, properties): 连接数据库的方法。
url: 连接数据库的URL
URL语法:jdbc:mysql://localhost:3306/day11
jdbc协议:数据库子协议://主机:端口/数据库
user: 数据库的用户名
password: 数据库用户密码
|- DriverManager类: 驱动管理器类,用于管理所有注册的驱动程序
|-registerDriver(driver) : 注册驱动类对象
|-Connection getConnection(url,user,password); 获取连接对象
|- Connection接口: 表示java程序和数据库的连接对象。(面向对象思想)
|- Statement createStatement() : 创建Statement对象
|- PreparedStatement prepareStatement(String sql) 创建PreparedStatement对象
|- CallableStatement prepareCall(String sql) 创建CallableStatement对象
|- setAutoCommit(boolean autoCommit) ; 设置事务是否自动提交
如果设置为false,表示手动提交事务。
|- void commit() ; 手动提交事务
|- rollback() ; 回滚(出现异常时候,所有已经执行成功的代码需要回退到事务开始前的状态。)
|- setSavepoint(String name) 设置回滚点
|- Statement接口: 用于执行静态的sql语句
|- int executeUpdate(String sql) : 执行静态的更新sq l语句(DDL,DML)
|- ResultSet executeQuery(String sql) :执行的静态的查询sql语句(DQL)
|--批处理相关方法
void addBatch(String sql) 添加批处理
void clearBatch() 清空批处理
int[] executeBatch() 执行批处理
|-PreparedStatement接口:用于执行预编译sql语句
|- int executeUpdate() : 执行预编译的更新sql语句(DDL,DML)
|-ResultSet executeQuery() : 执行预编译的查询sql语句(DQL)
|-CallableStatement接口:用于执行存储过程的sql语句(call xxx)
|-ResultSet executeQuery() : 调用存储过程的方法
CallableStatement继承PreparedStatement接口。
PreparedStatement接口继承Statement接口。
|- ResultSet接口:用于封装查询出来的数据
|- boolean next() : 将光标移动到下一行
|-getXX() : 获取列的值
每个驱动程序类必须实现的接口。
这个接口是JDBC规范中的一个核心接口。数据库厂商提供的数据库驱动必须
要实现这个接口。
+++ DriverManager类:
1.驱动管理器类, 用于管理多个JDBC驱动程序的基本服务。
2.这个API的常用方法:
DriverManager.registerDriver(new Driver())
DriverManager.getConnection(url, user, password),
3.注意:在实际开发中并不推荐采用registerDriver方法注册驱动。原因有二:
一、查看Driver的源代码可以看到,如果采用此种方式,会导致驱动程序注册两次,也就是在内存中会有两个Driver对象。
二、程序依赖mysql的api,脱离mysql的jar包,程序将无法编译,将来程序切换底层数据库将会非常麻烦。
推荐方式:Class.forName(“com.mysql.jdbc.Driver”);
采用此种方式不会导致驱动对象在内存中重复出现,并且采用此种方式,程序仅仅只需要一个字符串,不需要依赖具体的驱动,使程序的灵活性更高。
同样,在开发中也不建议采用具体的驱动类型指向getConnection方法返回的connection对象。
Jdbc程序中的Connection,它用于代表数据库的链接。
Collection是数据库编程中最重要的一个对象,客户端与数据库所有交互都是通
过connection对象完成的。
这个对象的常用方法:
createStatement():创建向数据库发送sql的statement对象。
prepareStatement(sql) :创建向数据库发送预编译sql的PrepareSatement对象。
prepareCall(sql):创建执行存储过程的CallableStatement对象。
setAutoCommit(boolean autoCommit):设置事务是否自动提交。
commit() :在链接上提交事务。
rollback() :在此链接上回滚事务。
Statement对象用于向数据库发送SQL语句。
Statement对象常用方法:
executeQuery(String sql) :用于向数据发送查询语句。
executeUpdate(String sql):用于向数据库发送insert、update或delete语句
execute(String sql):用于向数据库发送任意sql语句
addBatch(String sql) :把多条sql语句放到一个批处理中。
executeBatch():向数据库发送一批sql语句执行。
ResultSet:表示查询数据的结果集。
该对象内部维护了一个光标,指向结果集的第一行之前
+++ next()方法,判断下一行是否有数据,
如果有数据,返回true,并将光标指向下一行
如果没有,返回false。
通过该方法,可以遍历结果集。
ResultSet rs = st.executeQuery(sql);
while(rs.next()) {
//根据索引值获取行数据
int id = rs.getInt(1);
String name = rs.getString(2);
}
+++获取行数据
...
getShort(..)
getInt(..)
getString(..), 获取行数据。
注意:列的索引从1开始。
|- Statement接口: 用于执行静态的sql语句
|- int executeUpdate(String sql) : 执行静态的更新sq l语句(DDL,DML)
|- ResultSet executeQuery(String sql) :执行的静态的查询sql语句(DQL)
/**
* 执行DDL语句(创建表)
*/
@Test
public void test1(){
Statement stmt = null;
Connection conn = null;
try {
//1.驱动注册程序
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象
conn = DriverManager.getConnection(url, user, password);
//3.创建Statement
stmt = conn.createStatement();
//4.准备sql
String sql = "CREATE TABLE student(id INT PRIMARY KEY AUTO_INCREMENT,NAME VARCHAR(20),gender VARCHAR(2))";
//5.发送sql语句,执行sql语句,得到返回结果
int count = stmt.executeUpdate(sql);
//6.输出
System.out.println("影响了"+count+"行!");
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally{
//7.关闭连接(顺序:后打开的先关闭)
if(stmt!=null)
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
if(conn!=null)
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
/**
* 使用Statement执行DML语句
* @author APPle
*
*/
public class Demo2 {
private String url = "jdbc:mysql://localhost:3306/day17";
private String user = "root";
private String password = "root";
/**
* 增加
*/
@Test
public void testInsert(){
Connection conn = null;
Statement stmt = null;
try {
//通过工具类获取连接对象
conn = JdbcUtil.getConnection();
//3.创建Statement对象
stmt = conn.createStatement();
//4.sql语句
String sql = "INSERT INTO student(NAME,gender) VALUES('李四','女')";
//5.执行sql
int count = stmt.executeUpdate(sql);
System.out.println("影响了"+count+"行");
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally{
//关闭资源
/*if(stmt!=null)
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
if(conn!=null)
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}*/
JdbcUtil.close(conn, stmt);
}
}
/**
* 修改
*/
@Test
public void testUpdate(){
Connection conn = null;
Statement stmt = null;
//模拟用户输入
String name = "陈六";
int id = 3;
try {
/*//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象
conn = DriverManager.getConnection(url, user, password);*/
//通过工具类获取连接对象
conn = JdbcUtil.getConnection();
//3.创建Statement对象
stmt = conn.createStatement();
//4.sql语句
String sql = "UPDATE student SET NAME='"+name+"' WHERE id="+id+"";
System.out.println(sql);
//5.执行sql
int count = stmt.executeUpdate(sql);
System.out.println("影响了"+count+"行");
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally{
//关闭资源
/*if(stmt!=null)
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
if(conn!=null)
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}*/
JdbcUtil.close(conn, stmt);
}
}
/**
* 删除
*/
@Test
public void testDelete(){
Connection conn = null;
Statement stmt = null;
//模拟用户输入
int id = 3;
try {
//通过工具类获取连接对象
conn = JdbcUtil.getConnection();
//3.创建Statement对象
stmt = conn.createStatement();
//4.sql语句
String sql = "DELETE FROM student WHERE id="+id+"";
System.out.println(sql);
//5.执行sql
int count = stmt.executeUpdate(sql);
System.out.println("影响了"+count+"行");
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally{
JdbcUtil.close(conn, stmt);
}
}
}
ResultSet:表示查询数据的结果集。该对象内部维护了一个光标,指向结果集的第一行之前
+++ next()方法,判断下一行是否有数据,
如果有数据,返回true,并将光标指向下一行
如果没有,返回false。
通过该方法,可以遍历结果集。
ResultSet rs = st.executeQuery(sql);
while(rs.next()) {
//根据索引值获取行数据
int id = rs.getInt(1);
String name = rs.getString(2);
}
+++获取行数据
方式一:根据索引值获取行数据(索引值从1开始)
getString(1)
getInt(2)
getShort(3)
方式二:根据列名获取行数据
getString("name")
getInt("age")
getShort("sex")
方式一:根据索引值获取行数据(索引值从1开始)
根据索引值获取行数据,该方式虽然推荐使用,效率高。
但是在实际应用中,还是使用列名获取行数据。
原因:如果表的字段特别多,使用索引值取值,容易错乱。
/**
* 查询数据
* @throws SQLException
*/
@Test
public void select() throws SQLException {
Connection conn = JdbcUtil.getConn();
Statement st=null;
try {
st = conn.createStatement();
String sql="select * from stu";
ResultSet rs = st.executeQuery(sql);
while(rs.next()) {
//根据索引值获取行数据
int id = rs.getInt(1);
String name = rs.getString(2);
int age = rs.getInt(3);
System.out.println(id+" - "+name+" - "+age);
}
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
JdbcUtil.close(conn,st,rs);
}
}
方式二:根据列名称获取行数据
/**
* 查询数据
* @throws SQLException
*/
@Test
public void select() throws SQLException {
Connection conn = JdbcUtil.getConn();
Statement st=null;
try {
st = conn.createStatement();
String sql="select * from stu";
ResultSet rs = st.executeQuery(sql);
while(rs.next()) {
//根据字段名称获取行数据
int id1 = rs.getInt("id");
String name1 = rs.getString("name");
int age1 = rs.getInt("age");
System.out.println(id1+" - "+name1+" - "+age1);
}
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
JdbcUtil.close(conn,st,rs);
}
}
/**
* 使用Statement执行DQL语句(查询操作)
* @author APPle
*/
public class Demo3 {
@Test
public void test1(){
Connection conn = null;
Statement stmt = null;
try{
//获取连接
conn = JdbcUtil.getConn();
//创建Statement
stmt = conn.createStatement();
//准备sql
String sql = "SELECT * FROM student";
//执行sql
ResultSet rs = stmt.executeQuery(sql);
//移动光标
/*boolean flag = rs.next();
flag = rs.next();
flag = rs.next();
if(flag){
//取出列值
//索引
int id = rs.getInt(1);
String name = rs.getString(2);
String gender = rs.getString(3);
System.out.println(id+","+name+","+gender);
//列名称
int id = rs.getInt("id");
String name = rs.getString("name");
String gender = rs.getString("gender");
System.out.println(id+","+name+","+gender);
}*/
//遍历结果
while(rs.next()){
int id = rs.getInt("id");
String name = rs.getString("name");
String gender = rs.getString("gender");
System.out.println(id+","+name+","+gender);
}
}catch(Exception e){
e.printStackTrace();
throw new RuntimeException(e);
}finally{
JdbcUtil.close(conn, stmt);
}
}
}
可以设置参数,参数从1开始:
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, "李四");
stmt.setString(2, "男");
public class Demo1 {
/**
* 增加
*/
@Test
public void testInsert() {
Connection conn = null;
PreparedStatement stmt = null;
try {
//1.获取连接
conn = JdbcUtil.getConnection();
//2.准备预编译的sql
String sql = "INSERT INTO student(NAME,gender) VALUES(?,?)"; //?表示一个参数的占位符
//3.执行预编译sql语句(检查语法)
stmt = conn.prepareStatement(sql);
//4.设置参数值
/**
* 参数一: 参数位置 从1开始
*/
stmt.setString(1, "李四");
stmt.setString(2, "男");
//5.发送参数,执行sql
int count = stmt.executeUpdate();
System.out.println("影响了"+count+"行");
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
JdbcUtil.close(conn, stmt);
}
}
/**
* 修改
*/
@Test
public void testUpdate() {
Connection conn = null;
PreparedStatement stmt = null;
try {
//1.获取连接
conn = JdbcUtil.getConnection();
//2.准备预编译的sql
String sql = "UPDATE student SET NAME=? WHERE id=?"; //?表示一个参数的占位符
//3.执行预编译sql语句(检查语法)
stmt = conn.prepareStatement(sql);
//4.设置参数值
/**
* 参数一: 参数位置 从1开始
*/
stmt.setString(1, "王五");
stmt.setInt(2, 9);
//5.发送参数,执行sql
int count = stmt.executeUpdate();
System.out.println("影响了"+count+"行");
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
JdbcUtil.close(conn, stmt);
}
}
/**
* 删除
*/
@Test
public void testDelete() {
Connection conn = null;
PreparedStatement stmt = null;
try {
//1.获取连接
conn = JdbcUtil.getConnection();
//2.准备预编译的sql
String sql = "DELETE FROM student WHERE id=?"; //?表示一个参数的占位符
//3.执行预编译sql语句(检查语法)
stmt = conn.prepareStatement(sql);
//4.设置参数值
/**
* 参数一: 参数位置 从1开始
*/
stmt.setInt(1, 9);
//5.发送参数,执行sql
int count = stmt.executeUpdate();
System.out.println("影响了"+count+"行");
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
JdbcUtil.close(conn, stmt);
}
}
/**
* 查询
*/
@Test
public void testQuery() {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
//1.获取连接
conn = JdbcUtil.getConnection();
//2.准备预编译的sql
String sql = "SELECT * FROM student";
//3.预编译
stmt = conn.prepareStatement(sql);
//4.执行sql
rs = stmt.executeQuery();
//5.遍历rs
while(rs.next()){
int id = rs.getInt("id");
String name = rs.getString("name");
String gender = rs.getString("gender");
System.out.println(id+","+name+","+gender);
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
//关闭资源
JdbcUtil.close(conn,stmt,rs);
}
}
}
创建数据库
设置id为主键,并且为自增长
CREATE TABLE `mem` (
`id` INT(11) PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(20) DEFAULT NULL,
`pwd` VARCHAR(20) DEFAULT NULL
) ENGINE=INNODB DEFAULT CHARSET=utf8
代码:
@Test
public void test() {
Connection conn = JdbcUtil.getConn();
String sql="insert into mem(name,pwd) values(?,?)";
PreparedStatement pst=null;
try {
pst = conn.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
pst.setString(1, "小白");
pst.setString(2, "1234");
//执行sql
pst.executeUpdate();
//获取自增长列的值
ResultSet rs = pst.getGeneratedKeys();
while(rs.next()) {
int id = rs.getInt(1);
System.out.println("自增长id为:"+id);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
假设我们插入100个学生数据,如果使用pst执行sql,一个一个的插入学生的数据,
肯定是消耗数据资源的。所以这时候我们可以采用批量处理。
|-- Statement
批处理相关方法
void addBatch(String sql) 添加批处理
void clearBatch() 清空批处理
int[] executeBatch() 执行批处理
批量新增100个学生
//批量新增100个学生
@Test
public void demo11() {
String sql="insert into student(name,chinese) values(?,?)";
Connection conn = JdbcUtil.getConn();
PreparedStatement pst=null;
try {
pst = conn.prepareStatement(sql);
//设置参数
for(int i=0;i<100;i++) {
pst.setString(1, "xiao"+i);
pst.setInt(2, i);
//新增批量处理
pst.addBatch(); //不传入sql
}
//执行批量处理
pst.executeBatch();
//清空批量处理
pst.clearBatch();
} catch (SQLException e) {
e.printStackTrace();
}finally {
try {
pst.close();
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
案例1
public static void batchInsert() throws SQLException {
long start = System.currentTimeMillis();
Connection conn = getConnection();
conn.setAutoCommit(false);
PreparedStatement ps = null;
String sql = "insert into xxx values (?,'1','1')";
ps = conn.prepareStatement(sql); // 批量插入时ps对象必须放到for循环外面
for (int i=0;i < 50000;i++){
ps.setString(1, i+"");
ps.addBatch();
// 每1000条记录插入一次
if (i % 1000 == 0){
ps.executeBatch();
conn.commit();
ps.clearBatch();
}
}
// 剩余数量不足1000
ps.executeBatch();
conn.commit();
ps.clearBatch();
long end = System.currentTimeMillis();
System.out.println(end - start);
}
案例2
private void saveBatch(List<SunshineJcxxb> list, LoginUser user)
{
log.info("---------------准备导入----------------------");
SqlSession sqlSession = SqlSessionUtils.getSqlSession(
sqlSessionTemplate.getSqlSessionFactory(), sqlSessionTemplate.getExecutorType(),
sqlSessionTemplate.getPersistenceExceptionTranslator());
PreparedStatement pst = null;
try
{
pst = sqlSession.getConnection().prepareStatement(
"INSERT INTO phjr_data.t_znyx_sunshine_jcxxb " + "( id, task_id, house_number, cust_name, iden_no, "
+ "relation_name, phone,age,sex) VALUES (?, ?, ?, ?, ?, ?, ?,?,?)");
int i = 0;
log.info("---------------开始导入----------------------");
for (SunshineJcxxb baseInfo : list)
{
int j = 1;
pst.setString(j++ , baseInfo.getId());
pst.setString(j++ , baseInfo.getTaskId());
pst.setString(j++ , baseInfo.getHouseNumber());
pst.setString(j++ , baseInfo.getCustName());
pst.setString(j++ , baseInfo.getIdenNo());
pst.setString(j++ , baseInfo.getRelationName());
pst.setString(j++ , baseInfo.getPhone());
pst.setInt(j++ , baseInfo.getAge());
pst.setString(j++ , baseInfo.getSex());
pst.addBatch();
i++ ;
if (i % 100 == 0)
{
pst.executeBatch(); // Execute every 1000 items
}
}
pst.executeBatch();
log.info("---------------导入结束----------------------");
}
catch (SQLException e)
{
log.error(e.getMessage(),e);
}
finally
{
if (pst != null)
{
try
{
pst.close();
}
catch (SQLException e)
{
log.error(e.getMessage(),e);
}
}
if (sqlSession != null)
{
SqlSessionUtils.closeSqlSession(sqlSession,
sqlSessionTemplate.getSqlSessionFactory());
}
}
}
+++ 什么是大文本数据?
在实际开发中,程序需要把长文本(txt文件,存储大文本)或二进制数据
(存储二进制数据,例如图像、声音、二进制文)保存到数据库。
长文本数据:指的就是文章、小说、txt文件内容等大篇字符。
二进制数据:指的就是图像、声音、二进制文等。
+++ 大文本数据类型
Oracle中大文本数据类型,
Clob 长文本类型 (MySQL中不支持,使用的是text)
Blob 二进制类型
MySQL数据库大文本数据类型,
Text 长文本类型 : TINYTEXT、TEXT、MEDIUMTEXT和LONGTEXT
Blob 二进制类型 : TINYBLOB、BLOB、MEDIUMBLOB和LONGBLOB
创建数据库
CREATE TABLE test(
id INT PRIMARY KEY AUTO_INCREMENT,
content LONGTEXT , -- 大文本数据类型
img LONGBLOB -- 二进制数据类型
)
//保存大文本数据类型
@Test
public void test3() {
Connection conn=null;
PreparedStatement pst=null;
try {
String sql="insert into test(content) values(?)";
conn = JdbcUtil.getConn();
pst = conn.prepareStatement(sql);
//设置参数值
FileReader reader=new FileReader("C:\\Test\\a.txt");
pst.setCharacterStream(1, reader);
//执行
pst.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
}
//获取大文本数据类型
@Test
public void test4() {
Connection conn=null;
PreparedStatement pst=null;
try {
String sql="select * from test where id =?";
conn = JdbcUtil.getConn();
pst = conn.prepareStatement(sql);
//设置参数值
pst.setInt(1, 1);
//执行
ResultSet rs = pst.executeQuery();
while(rs.next()) {
//方式一:
Reader reader = rs.getCharacterStream("content");
//方式二:
String string = rs.getString("content");
System.out.println(string);
}
} catch (Exception e) {
e.printStackTrace();
}
}
//保存二进制数据类型
@Test
public void test33() {
Connection conn=null;
PreparedStatement pst=null;
try {
String sql="insert into test(img) values(?)";
conn = JdbcUtil.getConn();
pst = conn.prepareStatement(sql);
//设置参数值
FileInputStream in=new FileInputStream("C:\\Users\\BGS\\Desktop\\m.jpg");
pst.setBinaryStream(1, in);
//执行
pst.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
}
//获取二进制数据类型
@Test
public void test44() {
Connection conn=null;
PreparedStatement pst=null;
try {
String sql="select * from test where id =?";
conn = JdbcUtil.getConn();
pst = conn.prepareStatement(sql);
//设置参数值
pst.setInt(1, 2);
//执行
ResultSet rs = pst.executeQuery();
while(rs.next()) {
//方式一:
InputStream in = rs.getBinaryStream("img");
//方式二:
String string = rs.getString("img");
System.out.println(string);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class Demo {
private String url="jdbc:mysql://localhost:3306/day11";
private String user="root";
private String password="root";
/**
* Statement 执行静态sql
*
* @throws ClassNotFoundException
* @throws SQLException
*/
@Test
public void test_login() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(url, user, password);
//模拟用户登录,输入账号密码
String user="hlp";
String pwd="1234";
//模拟用户登录,sql入侵
String user1="hlp";
String pwd1=" ' or 1=1 -- ";
String sql=String.format("select * from mem where name='%s' and pwd='%s'",user1,pwd1);
Statement st=null;
ResultSet rs=null;
try {
st = conn.createStatement();
rs = st.executeQuery(sql);
while(rs.next()) {
System.out.println("登录ID:"+rs.getInt("id"));
}
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
rs.close();
st.close();
conn.close();
}
}
/**
* PreparedStatement:执行动态sql
*
* @throws ClassNotFoundException
* @throws SQLException
*/
@Test
public void test_login2() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(url, user, password);
//模拟用户登录,输入账号密码
String user="hlp";
String pwd="1234";
//模拟用户登录,sql入侵
String user1="hlp";
String pwd1=" ' or 1=1 -- ";
String sql="select * from mem where name=? and pwd=?";
PreparedStatement pst=null;
ResultSet rs=null;
try {
pst = conn.prepareStatement(sql);
pst.setString(1, user1);
pst.setString(2, pwd1);
rs = pst.executeQuery();
while(rs.next()) {
System.out.println("登录ID:"+rs.getInt("id"));
}
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
rs.close();
pst.close();
conn.close();
}
}
}
sql侵入的原理:
使用statement执行的sql,只能是静态sql。
静态sql说白了就是用户输入用户名和密码,然后拼接的sql。
所以用户可以手动拼接一些条件。
阻值sql侵入的原理:
使用预编译sql。PreparedStatement。
PreparedStatement有一个方法,setString(..),其作用就是设置入参。
设置入参时,还会参数中的 ' 进行了转义。所以对于输入的用户名、密码,
用户无法注释掉。
设置预编译sql的参数,从1开始:
pst.setString(1,obj )
pst.setInt(2,obj)
...
public List<Menu> listByPage(int pageNum,int pageSize) throws SQLException {
List<Menu> result=new ArrayList<Menu>();
Connection conn = JdbcUtils.getConn();
String sql="select * from menu limit ?,?";
PreparedStatement pst = conn.prepareStatement(sql);
pst.setInt(1,0);
pst.setInt(2, 50);
ResultSet rs = pst.executeQuery();
while(rs.next()) {
Menu m=new Menu();
m.setMenuId(rs.getString(1));
m.setMenuName(rs.getString(2));
m.setMenuId(rs.getString(3));
m.setUrl(rs.getString(4));
result.add(m);
}
return result;
}
获取行数据有两种方式:
方式一:根据索引值获取行数据(索引值从1开始)
getString(1)
getInt(2)
getShort(3)
方式二:根据列名获取行数据
getString("name")
getInt("age")
getShort("sex")
1)语法不同:PreparedStatement可以使用占位符,执行预编译的sql,而Statment只能使用静态的sql。
动态sql:可以预编译
静态sql:sql只能拼接。
2)效率不同: PreparedStatement可以使用sql缓存区,效率比Statment高(在mysql中,效率是一样的。)
3)安全性不同: PreparedStatement可以有效防止sql注入,而Statment不能防止sql注入。
推荐使用PreparedStatement
执行过程
Statement 在执行sql语句时,
1)首先会把sql语句发送到服务器端,
select * from stu where id=1
2)服务器端检查语法以及权限,
3)服务器把sql加入到sql缓冲区
sql缓冲区相当于一个map
Map,执行任务>
select * from stu where id=1 , 执行任务1
4)然后从sql缓冲区中取出sql执行。
5)再次执行sql语句时,由于在缓冲区map中获取不到sql,所以会重新加入。
select * from stu where id=2。
PreparedStatement 在执行sql语句时,
1)首先会把sql语句发送到服务器端。
select * from stu where id=?
2)服务器端检查语法以及权限,
3)服务器把sql加入到sql缓冲区
sql缓冲区相当于一个map
Map,执行任务>
select * from stu where id=1 , 执行任务1
4)然后客户端传参,从sql缓冲区中取出sql执行。
5)再次执行sql语句时,由于在缓冲区map中可以获取到sql,所以直接传参即可执行。
select * from stu where id=?
综上,如果数据库支持sql缓冲区,
那么使用PreparedStatement要比statement效率高。因为PreparedStatement支持sql缓冲区。
如果数据库不支持sql缓冲区,
那么PreparedStatement和statement效率是一样的。
数据库sql缓冲区
支持sql缓冲区:
oracle 、sql server
不支持sql缓冲区:
mysql
结论:并不是所有的数据库都支持sql缓冲区这一部分,
所以对mysql来说,PreparedStatemt对sql缓冲区的优化在mysql中是无效的。
即是在mysql中,PreparedStatemt和Statement的效率一样高。
mysql数据库的注释
SELECT * FROM men WHERE id=1 -- 注释
SELECT * FROM men WHERE id=1 -- or 1=1
关于sql注入的代码演示
//模拟用户登录
@Test
public void login() {
Demo d=new Demo();
String name="hlp";
//输入正常登录的密码
String pwd1="1234";
//输入sql注入的密码
String pwd2="1' or 1=1 -- ";
//使用statement测试sql注入
//--登录失败
d.testPwd(name, pwd2);
//使用preparedStatemnt测试sql注入
//--登录成功
d.testPwd2(name, pwd2);
}
/**
* statment对象执行sql
*
* @param name
* @param pwd
*/
public void testPwd(String name ,String pwd) {
Connection conn = JdbcUtil.getConn();
String sql="select * from mem where name='"+name+"' and pwd='"+pwd+"'";
System.out.println(sql);
Statement st=null;
ResultSet rs =null;
try {
st = conn.createStatement();
rs = st.executeQuery(sql);
boolean isExist = rs.next();
System.out.println(isExist?"密码正确":"密码错误");
} catch (SQLException e) {
JdbcUtil.close(conn, st, rs);
}
}
/**
* preparedStatement对象查询sql
*
* @param name
* @param pwd
*/
public void testPwd2(String name ,String pwd) {
Connection conn = JdbcUtil.getConn();
String sql="select * from mem where name=? and pwd=?";
System.out.println(sql);
PreparedStatement pst=null;
ResultSet rs =null;
try {
pst = conn.prepareStatement(sql);
//设置参数
pst.setString(1, name);
pst.setString(2, pwd);
rs = pst.executeQuery();
boolean isExist = rs.next();
System.out.println(isExist?"密码正确":"密码错误");
} catch (SQLException e) {
e.printStackTrace();
JdbcUtil.close(conn, pst, rs);
}
}
sql注入的原理
1.使用mysql的注释,来注释有效的代码。
2.客户端发送sql到数据库,由于sql本身就是拼接的,
所以,我们可以在输入密码的时候,在密码中我们使用mysql注释来注释有效的sql片段,并拼接一些条件。
原有的sql: select * from mem where name='hlp' and pwd='1'
被注入的sql: select * from mem where name='hlp' and pwd='1' or 1=1 -- '
PreparedStatement和Statement对sql注入的支持
对于JDBC而言,
SQL注入攻击只对Statement有效,对PreparedStatement是无效的,
原理:
mysql驱动类的底层的setString()方法,有三个作用:
1.用值替换?
2.在值的两边加上了单引号
3.把值中所有的单引号替换成了斜杠单引号,进行了转义。
说白了,这个方法的作用就是把值当中的所有单引号给转义了!这就达到了防止sql注入的目的。
详细:
mysql驱动类的PreparedStatement实现类的setStrig(),方法内部做了单引
号的转义,而Statement不能防止sql注入,就是因为它没有把单引号做转
义,而是简单粗暴的直接拼接字符串,所以达不到防止sql注入的目的。
所以,不管是mysql数据库产商的PreparedStatement实现类还是sqlserver数据库
产商的PreparedStatement实现类,其实底层的原理都差不多,当然啦,我们直接
在java程序中把那些单引号直接转义也行,只不过比较繁琐一点罢了,既然各个数
据库产商的PreparedStatement实现类的setString()方法中已经做了转义
# 相当于对数据 加上 双引号,$ 相当于直接显示数据。
一句话言简意赅。分条陈述:
# 能够sql注入问题,$ 不行;
$ 用于传入数据库对象,例如表名;
能用 # 就不要使用 $;
String sql="call pro_sel(?,?,?)";
第一个?输入参数,第二个?输入参数,第三个?输出参数。
1.设置预编译sql的参数:
设置存储过程中的输入参数: cst.setInt(1,1);
cst.setString(2,'小白');
设置存储过程中的输出参数:
cst.registerOutParameter(3, java.sql.Types.VARCHAR);
2.获取存储过程中输出参数
cst.getString(3);
3.使用CallableStatement调用存储过程,执行预编译的存储过程sql时,
都使用 cst.executeQuery()来执行。
存储过程
DELIMITER &
CREATE PROCEDURE pro_sel(IN sid INT )
BEGIN
SELECT * FROM mem WHERE id =sid;
END &
代码
@Test
public void test1() {
Connection conn=null;
CallableStatement cst=null;
ResultSet rs=null;
try {
//获取连接对象
conn = JdbcUtil.getConn();
//准备sql
String sql="call pro_sel(?)";
//预编译sql
cst = conn.prepareCall(sql);
//设置输入参数
cst.setInt(1, 1);
//发送参数,执行
//注意:所有调用存储过程的sql语句都使用executeQuery方法来执行。
rs= cst.executeQuery();
//遍历结果集
while(rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String pwd = rs.getString("pwd");
System.out.println(id+"-"+name+"-"+pwd);
}
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException();
}finally {
JdbcUtil.close(conn, cst,rs);
}
}
存储过程
DELIMITER &
CREATE PROCEDURE pro_sel(IN sid INT ,OUT sname VARCHAR(20))
BEGIN
SELECT NAME INTO sname FROM mem WHERE id =sid;
END &
代码:
@Test
public void test2() {
Connection conn=null;
CallableStatement cst=null;
try {
//获取连接对象
conn = JdbcUtil.getConn();
//准备sql
String sql="call pro_sel(?,?)";
//预编译sql
cst = conn.prepareCall(sql);
//设置参数
//1.设置输入参数
cst.setInt(1, 1);
//2.设置输出参数
cst.registerOutParameter(2, java.sql.Types.VARCHAR);
//发送参数,执行
//注意:所有调用存储过程的sql语句都使用executeQuery方法来执行。
cst.executeQuery(); //结果不是返回到结果集中,而是返回到输出参数中
//获取结果
String sname = cst.getString(2);
System.out.println(sname);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException();
}finally {
JdbcUtil.close(conn, cst);
}
}
sql执行完毕,Connection接口需要执行关闭。
原因:
sql执行完毕,Connection对象就会被JVM回收。但是由于Connection对象是
java和mysql服务端的连接对象,即使Connection对象被JVM回收,mysq服务器端的
连接依旧存在,此时,当连接增多时,数据库服务器就会出现内存溢出的情况。
关闭连接,要依次关闭。关闭Connection接口顺序:
1.关闭result
2.关闭statement
3.关闭connection
/**
* 关闭连接
* @param conn
* @param st
*/
public static void close(Connection conn,Statement st,ResultSet rs) {
try {
//关闭resulset
if(rs!=null) {
rs.close();
}
//关闭statement
if(st!=null) {
st.close();
}
//关闭连接对象
if(conn!=null) {
conn.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}