JDBC(二) 综合案列、SQL注入问题、封装工具类、ORM
目录
五、综合案例
5.1 准备表
5.2 实现注册和登录操作
六、SQL注入问题
6.1 问题介绍
6.2 PreparedStatement
七、封装工具类
7.1 封装获取连接&释放资源操作
7.2 跨平台方案
八、ORM
九、DAO数据访问对象层
9.1 准备关系表
9.2 准备实体类
9.3 准备DAO接口与实现类
9.4 在main方法中测试
五、综合案例
完成一个注册登录的小案例。
5.1 准备表
创建一张用户表userid,数值类型,主键,自增username,用户名,字符串类型,唯一,非空password,密码,字符串类型,非空create table user(id bigint primary key auto_increment comment '主键id',username varchar(32) unique not null comment '用户名',password varchar(32) not null comment '密码' )comment '用户表'; 查询2条测试数据insert into user values (1,'admin','admin'),(2,'zhangsan','123456');
5.2 实现注册和登录操作
注册驱动这个事情,只需要做一次就足够了。
注册驱动的本质就是将MySQL的Driver对象存放到DriverManager中的一个List集合中。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Scanner;
/*** 实现注册和登录操作*/
public class Demo3 {
public static void main(String[] args) throws Exception {// 因为注册驱动的操作,做一次就够了,他就是将Driver的对象扔到DriverManager的一个List集合中Class.forName("com.mysql.cj.jdbc.Driver");Scanner input = new Scanner(System.in);while(true){// 基于提示,做具体什么操作System.out.println("当前可以选择注册或者登录");System.out.println("注册操作输入 1");System.out.println("登录操作输入 2");int i = input.nextInt();
// 因为无论是注册还是登录,都需要用户输入用户名和密码System.out.println("请输入用户名:");String username = input.next();System.out.println("请输入密码:");String password = input.next();
// 根据i执行不同的逻辑if(i == 1){// 注册操作//1、获取链接Connection conn =DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test?characterEncoding=UTF-8", "root", "root");//2、拿到statementStatement statement = conn.createStatement();//3、准备注册的insert的SQLString sql = "insert into user (username,password) values ('"+username+"','"+password+"');";System.out.println("注册要执行的SQL:" + sql);//4、执行SQLint count = statement.executeUpdate(sql);//5、根据返回结果基于提示if(count == 1){System.out.println("注册成功!!");}//6、释放资源statement.close();conn.close();}else {// 登录操作//1、获取链接Connection conn =DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test?characterEncoding=UTF-8", "root", "root");//2、拿到statementStatement statement = conn.createStatement();//3、准备登录的查询SQLString sql = "select * from user where username='"+username+"' and password='"+password+"';";System.out.println("登录要执行的SQL:" + sql);//4、执行SQLResultSet resultSet = statement.executeQuery(sql);if (resultSet.next()){// 到这,说明用户名和密码正确,登录成功!System.out.println("用户名和密码正确,登录成功!");}else{// 到这,说明用户名和密码错误,登录失败!System.out.println("用户名和密码错误,登录失败!");}//5、释放资源resultSet.close();statement.close();conn.close();
}}}
}
六、SQL注入问题
6.1 问题介绍
用户输入的数据中有SQL关键字或者语法,并且这些内容参与了SQL语句的编译,导致SQL语句编译后的条件含义为true,一致得到正确的结果,你输入的内容其实不是正确的。这种现象就成为 SQL注入 。
SQL注入是一个很经典的问题。 由于编写的SQL语句是在用户输入数据,整合后再进行编译的。所以为了避免SQL注入的问题,咱们要使SQL语句在用户输入数据前,就已经编译完成了完整的SQL语句,再进行填充数据。
6.2 PreparedStatement
PreparedStatement实现了Statement接口,执行SQL语句的方法,和最开始使用的Statement对象是一样的。
之前通过createStatement方法获取到的是
public class StatementImpl现在需要使用到的是,PreparedStatement的实现类
public interface PreparedStatement咱们也在PreparedStatement接口上的注释信息看到了基本的使用方式
将SQL语句中需要咱们填充的值,采用?代替,然后再通过PreparedStatement对象的setXxx方法,通过从1开始的索引位置,给每个?赋值。
PreparedStatement pstmt = con.prepareStatement("UPDATE EMPLOYEES SET SALARY = ? WHERE ID = ?"); pstmt.setBigDecimal(1, 153833.00) pstmt.setInt(2, 110592)
将前面的综合案例修改为使用PreparedStatement对象来解决之前的问题
import java.sql.*;
import java.util.Scanner;
/*** 实现注册和登录操作* 基于SQL注入的问题,在这里采用PreparedStatement来解决SQL注入问题。*/
public class Demo4 {
public static void main(String[] args) throws Exception {// 因为注册驱动的操作,做一次就够了,他就是将Driver的对象扔到DriverManager的一个List集合中Class.forName("com.mysql.cj.jdbc.Driver");Scanner input = new Scanner(System.in);while(true){// 基于提示,做具体什么操作System.out.println("当前可以选择注册或者登录");System.out.println("注册操作输入 1");System.out.println("登录操作输入 2");int i = input.nextInt();
// 因为无论是注册还是登录,都需要用户输入用户名和密码System.out.println("请输入用户名:");String username = input.next();System.out.println("请输入密码:");String password = input.next();
// 根据i执行不同的逻辑if(i == 1){// 注册操作//1、获取链接Connection conn =DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test?characterEncoding=UTF-8", "root", "root");//2、准备注册的insert的SQLString sql = "insert into user (username,password) values (?,?)";//3.1、拿到statementPreparedStatement ps = conn.prepareStatement(sql);//3.2 给占位符?赋值ps.setString(1,username);ps.setString(2,password);//4、执行SQLint count = ps.executeUpdate();//5、根据返回结果基于提示if(count == 1){System.out.println("注册成功!!");}//6、释放资源ps.close();conn.close();}else {// 登录操作//1、获取链接Connection conn =DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test?characterEncoding=UTF-8", "root", "root");//2、准备登录的查询SQLString sql = "select * from user where username = ? and password = ?";
//3.1、拿到statementPreparedStatement ps = conn.prepareStatement(sql);//3.2、给?赋值ps.setString(1,username);ps.setString(2,password);
//4、执行SQLResultSet resultSet = ps.executeQuery();if (resultSet.next()){// 到这,说明用户名和密码正确,登录成功!System.out.println("用户名和密码正确,登录成功!");}else{// 到这,说明用户名和密码错误,登录失败!System.out.println("用户名和密码错误,登录失败!");}//5、释放资源resultSet.close();ps.close();conn.close();
}}}
}
七、封装工具类
7.1 封装获取连接&释放资源操作
在实际使用JDBC的时候,很多操作都是固定的,没有必要每次都去注册驱动,获取链接对象等等。
同样,释放资源的close操作也可以封装一下
下面是封装好的具体工具类
import java.sql.*;
/*** 数据库操作的一个通用的内容*/
public class DatabaseUtils {
// 注册驱动的操作static{try {Class.forName("com.mysql.cj.jdbc.Driver");} catch (ClassNotFoundException e) {throw new RuntimeException(e);}}
/*** 获取Connection对象方法* @return*/public static Connection getConnection() {try {Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test?characterEncoding=utf-8", "root", "root");return connection;} catch (SQLException e) {e.printStackTrace();throw new RuntimeException("获取Connection出错!");}}
/*** 针对查询操作的释放资源* @param connection* @param statement* @param resultSet*/public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {if(resultSet == null || statement == null || connection == null){throw new NullPointerException("参数传递的connection,statement,resultSet不允许为NULL");}try {resultSet.close();statement.close();connection.close();} catch (SQLException e) {throw new RuntimeException(e);}
}/*** 针对写操作的释放资源* @param connection* @param statement*/public static void closeAll(Connection connection, Statement statement) {if( statement == null || connection == null){throw new NullPointerException("参数传递的connection,statement,resultSet不允许为NULL");}try {statement.close();connection.close();} catch (SQLException e) {throw new RuntimeException(e);}
}
}
再次迭代之前的Demo4为Demo5。
import com.mashibing.utils.DatabaseUtils;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Scanner;
/*** 实现注册和登录操作* 基于SQL注入的问题,在这里采用PreparedStatement来解决SQL注入问题。*/
public class Demo5 {
public static void main(String[] args) throws Exception {Scanner input = new Scanner(System.in);while(true){// 基于提示,做具体什么操作System.out.println("当前可以选择注册或者登录");System.out.println("注册操作输入 1");System.out.println("登录操作输入 2");int i = input.nextInt();
// 因为无论是注册还是登录,都需要用户输入用户名和密码System.out.println("请输入用户名:");String username = input.next();System.out.println("请输入密码:");String password = input.next();
// 根据i执行不同的逻辑if(i == 1){// 注册操作//1、获取链接Connection conn = DatabaseUtils.getConnection();//2、准备注册的insert的SQLString sql = "insert into user (username,password) values (?,?)";//3.1、拿到statementPreparedStatement ps = conn.prepareStatement(sql);//3.2 给占位符?赋值ps.setString(1,username);ps.setString(2,password);//4、执行SQLint count = ps.executeUpdate();//5、根据返回结果基于提示if(count == 1){System.out.println("注册成功!!");}//6、释放资源DatabaseUtils.closeAll(conn,ps);}else {// 登录操作//1、获取链接Connection conn = DatabaseUtils.getConnection();//2、准备登录的查询SQLString sql = "select * from user where username = ? and password = ?";
//3.1、拿到statementPreparedStatement ps = conn.prepareStatement(sql);//3.2、给?赋值ps.setString(1,username);ps.setString(2,password);
//4、执行SQLResultSet resultSet = ps.executeQuery();if (resultSet.next()){// 到这,说明用户名和密码正确,登录成功!System.out.println("用户名和密码正确,登录成功!");}else{// 到这,说明用户名和密码错误,登录失败!System.out.println("用户名和密码错误,登录失败!");}
//5、释放资源DatabaseUtils.closeAll(conn,ps,resultSet);
}}}
}
7.2 跨平台方案
现在将连接数据库的url,username,password都是写死在Java代码中的。
正常运行的项目需要编译成.class文件运行,编译过后再想修改url,username,password就挺难的。
咱们利用Hashtable的子类,Properties,将连接数据库的信息卸载外部的.properties文件中,在Java代码里,通过Properties类,将外部的.properties文件的内容加载到内存里使用。
分成几步准备
-
第一步,外部声明好一个database.properties文件,编写好连接数据库的信息
-
第二步,在Java的DatabaseUtils的工具类中,加载database.properties文件,读取连接信息
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
/*** 数据库操作的一个通用的内容*/
public class DatabaseUtils {
private static final Properties PROP = new Properties();
public static void main(String[] args) {DatabaseUtils db = new DatabaseUtils();}// 注册驱动的操作static{try {// 优先通过PROP对象,加载database.properties文件InputStream is = DatabaseUtils.class.getResourceAsStream("/database.properties");PROP.load(is);Class.forName(PROP.getProperty("jdbc.driver"));} catch (Exception e) {throw new RuntimeException(e);}}
/*** 获取Connection对象方法* @return*/public static Connection getConnection() {try {Connection connection = DriverManager.getConnection(PROP.getProperty("jdbc.url"),PROP.getProperty("jdbc.username"),PROP.getProperty("jdbc.password"));return connection;} catch (SQLException e) {e.printStackTrace();throw new RuntimeException("获取Connection出错!");}}// 省略释放资源代码!!!!
}
八、ORM
所谓的ORM,其实就是Object Relational Mapping,也就是对象关系映射。
O就代表你Java中的实体类,R就是你数据库中的具体表。
M就是让O和R建立一个联系。
本质的意愿,咱们在操作数据库时,需要用到SQL语句。ORM的愿景是希望使用面向对象的思维来和数据库交互,不需要了解SQL到底是什么。这种咱们成为 完整的ORM框架 能够提供的一个效果。
User findById(1); --- select * from user where id = 1; int save(User); --- insert into user (id,username,password) values (?,?,?);但是,这种完整的ORM框架会让咱们程序员对SQL的把控很低,现在国内主流的是一款 半自动化的ORM框架 ,这种ORM框架依然需要咱们写SQL语句。
而咱们不能直接干一个框架出来,现在的目的:
准备好对应jdbc-test库中的user表的一个实体类,User类。
在查询User表时,将查询到的结果封装到User类中操作。
准备好实体类User:
/*** 当前是一个实体类,目的是和数据库中的关系表产生映射关系*/
public class User {
// 全部的基本数据类型,采用包装类,因为包装类可以多存储一个NULL,从而尽可能的避免出现NullPointException/** 主键Id */private Long id;
/** 用户名 */private String username;
/** 密码 */private String password;
public User() {}
public User(Long id, String username, String password) {this.id = id;this.username = username;this.password = password;}
@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\'' +", password='" + password + '\'' +'}';}
public Long getId() {return id;}
public void setId(Long id) {this.id = id;}
public String getUsername() {return username;}
public void setUsername(String username) {this.username = username;}
public String getPassword() {return password;}
public void setPassword(String password) {this.password = password;}
}
将查询数据库表User的结果,映射到User实体类中
import com.ceshi.entity.User;
import com.ceshi.utils.DatabaseUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/*** 是为了完成查询user表后,将user表的ResultSet结果封装到User对象中*/
public class Demo6 {
public static void main(String[] args) throws SQLException {//1、获取连接对象Connection conn = DatabaseUtils.getConnection();
//2、准备查询user信息的SQLString sql = "select * from user where id = ?";
//3、获取PreparedStatement,传入SQLPreparedStatement ps = conn.prepareStatement(sql);
//4、给占位符赋值ps.setLong(1,1);
//5、执行SQL,获取ResultSet返回结果ResultSet rs = ps.executeQuery();
//6、遍历ResultSet结果,封装到Userwhile(rs.next()){User user = new User();user.setId(rs.getLong("id"));user.setUsername(rs.getString("username"));user.setPassword(rs.getString("password"));System.out.println("查询到的user表信息:" + user);}
//7、释放资源DatabaseUtils.closeAll(conn, ps, rs);}
}
九、DAO数据访问对象层
DAO层专门实现和数据库交互的操作,不参与任何逻辑。
DAO层单独封装出来的目的是为了达到解耦的效果,也就是让咱们的程序达到一个低耦合的效果。
对同一张表的增删改查操作都封装到一个XxxDao类中。
针对这个XxxDao类里提供对应的增删改查操作(insert、update、delete、findById、findAll)
现在就需要将XxxDaoImpl实现出来,如下图:
准备一个案例操作。
9.1 准备关系表
创建一张person表,提供下述字段:
id:int类型,主键自增
name:varchar类型,非空
age:int类型,非空
born_date:date类型,非空
email:varchar类型
address:varchar类型
create table person(id int primary key auto_increment comment '主键',name varchar(16) not null comment '名字',age int not null comment '年龄',born_date date not null comment '出生日期',email varchar(64) comment 'email',address varchar(256) comment '地址' );自己再准备几条测试数据
9.2 准备实体类
准备Person类映射前面构建好的person表
package com.ceshi.entity;
import java.util.Date;
/*** 映射jdbc_test库中的person表*/
public class Person {
private Integer id;
private String name;
private Integer age;
private Date bornDate;
private String email;
private String address;
// 省略 无参,有参,toString,get/set
}
9.3 准备DAO接口与实现类
先准备到PersonDao接口,提供抽象方法,对外提供哪些功能
package com.ceshi.dao;
import com.ceshi.entity.Person;
import java.util.List;
/*** 构建与Person表交互的DAO层接口*/
public interface PersonDao {
/*** 插入一条Person数据* @param person* @return*/int insert(Person person);
/*** 根据Id修改person信息* @param person* @return*/int updateById(Person person);
/*** 根据id删除一条person* @param id* @return*/int deleteById(Integer id);
/*** 根据id查询一条person信息* @param id* @return*/Person findById(Integer id);
/*** 查询全部person信息* @return*/List<Person> findAll();
}
提供Dao层对应的实现类。
package com.ceshi.dao.impl;
import com.ceshi.dao.PersonDao;
import com.ceshi.entity.Person;
import com.ceshi.utils.DatabaseUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/*** 实现PersonDao接口,实现内5个方法。*/
public class PersonDaoImpl implements PersonDao {
@Overridepublic int insert(Person person) throws SQLException {//1、获取连接Connection conn = DatabaseUtils.getConnection();//2、编写SQL语句String sql = "insert into person (name,age,born_date,email,address) values (?,?,?,?,?)";//3、获取PreparedStatement,传入SQLPreparedStatement ps = conn.prepareStatement(sql);//4、给占位符赋值ps.setString(1, person.getName());ps.setInt(2, person.getAge());ps.setDate(3,new java.sql.Date(person.getBornDate().getTime()));ps.setString(4,person.getEmail());ps.setString(5, person.getAddress());//5、执行SQL,获取结果int count = ps.executeUpdate();//6、释放资源DatabaseUtils.closeAll(conn,ps);//7、返回结果return count;}
@Overridepublic int updateById(Person person) throws SQLException {//1、获取连接Connection conn = DatabaseUtils.getConnection();
//2、编写SQL语句String sql = "update person set name=?,age=?,born_date=?,email=?,address = ?where id=?";
//3、获取PreparedStatement,传入SQLPreparedStatement ps = conn.prepareStatement(sql);
//4、给占位符赋值ps.setString(1, person.getName());ps.setInt(2, person.getAge());ps.setDate(3, new java.sql.Date(person.getBornDate().getTime()));ps.setString(4, person.getEmail());ps.setString(5, person.getAddress());ps.setInt(6, person.getId());
//5、执行SQL,获取结果int count = ps.executeUpdate();
//6、释放资源DatabaseUtils.closeAll(conn,ps);
//7、返回结果return count;}
@Overridepublic int deleteById(Integer id) throws SQLException {//1、获取连接Connection conn = DatabaseUtils.getConnection();
//2、编写SQL语句String sql = "delete from person where id = ?";
//3、获取PreparedStatement,传入SQLPreparedStatement ps = conn.prepareStatement(sql);
//4、给占位符赋值ps.setInt(1,id);
//5、执行SQL,获取结果int count = ps.executeUpdate();
//6、释放资源DatabaseUtils.closeAll(conn,ps);
//7、返回结果return count;}
@Overridepublic Person findById(Integer id) throws SQLException {//1、获取连接Connection conn = DatabaseUtils.getConnection();
//2、编写SQL语句String sql = "select * from person where id = ?";
//3、获取PreparedStatement,传入SQLPreparedStatement ps = conn.prepareStatement(sql);
//4、给占位符赋值ps.setInt(1,id);
//5、执行SQL,获取结果集ResultSetResultSet resultSet = ps.executeQuery();
//6、处理结果集封装数据Person person = null;if(resultSet.next()){person = new Person();person.setId(resultSet.getInt("id"));person.setName(resultSet.getString("name"));person.setAge(resultSet.getInt("age"));person.setBornDate(resultSet.getDate("born_date"));person.setEmail(resultSet.getString("email"));person.setAddress(resultSet.getString("address"));}
//7、释放资源DatabaseUtils.closeAll(conn,ps,resultSet);
//8、返回结果return person;}
@Overridepublic List<Person> findAll() throws SQLException {//1、获取连接Connection conn = DatabaseUtils.getConnection();
//2、编写SQL语句String sql = "select * from person";
//3、获取PreparedStatement,传入SQLPreparedStatement ps = conn.prepareStatement(sql);
//4、执行SQL,获取结果集ResultSetResultSet rs = ps.executeQuery();
//5、处理结果集封装数据List<Person> personList = new ArrayList<Person>();while (rs.next()){Person person = new Person();person.setId(rs.getInt("id"));person.setName(rs.getString("name"));person.setAge(rs.getInt("age"));person.setBornDate(rs.getDate("born_date"));person.setEmail(rs.getString("email"));person.setAddress(rs.getString("address"));// 记得封装完person,add到前面构建好的List集合中!personList.add(person);}
//6、释放资源DatabaseUtils.closeAll(conn,ps,rs);
//7、返回结果return personList;}
}
9.4 在main方法中测试
package com.mashibing; import java.sql.SQLException; /*** 为了测试DAO层的对象功能是否可以正常使用*/ public class Demo7 { public static void main(String[] args) throws SQLException {/*// 测试插入数据到Person表Person person = new Person();person.setName("赵六");person.setAge(26);person.setBornDate(new Date());person.setEmail("mashibing@gmail.com");person.setAddress("北京市海淀区北清路aaaa");// 准备Dao层对象PersonDao personDao = new PersonDaoImpl();int count = personDao.insert(person);System.out.println(count);*/ /*// 测试修改Person表中的数据Person person = new Person();person.setId(4);person.setName("赵六六");person.setAge(266);person.setBornDate(new Date());person.setEmail("mashibing@126.com");person.setAddress("北京市海淀区苏州街tttt");// 准备Dao层对象PersonDao personDao = new PersonDaoImpl();int count = personDao.updateById(person);System.out.println(count); */ /*// 测试删除Person表中的数据Integer id = 4;// 准备Dao层对象PersonDao personDao = new PersonDaoImpl();int count = personDao.deleteById(id);System.out.println(count); */ /*// 测试根据Id查询Person信息Integer id = 3;// 准备Dao层对象PersonDao personDao = new PersonDaoImpl();Person person = personDao.findById(id);System.out.println(person); */ /*// 测试查询全部数据PersonDao personDao = new PersonDaoImpl();List<Person> list = personDao.findAll();for (Person person : list) {System.out.println(person);}*/} }