文章目录
JDBC概述
- 全称:Java数据库连接
- 理解为用于执行SQL语句的JavaAPI
- JDBC定义接口规范,为关系型数据库提供统一访问方式
- 以前(左图):了解各个驱动程序的方法(具体写法)
- 现在(右图):各个驱动遵守一套标准:JDBC的接口规范(每个驱动实现此套规范)
- JDBC定义接口规范,具体的驱动提供具体的实现——》不用了解每个数据库驱动程序,只需了解JDBC接口规范
搭建开发环境
- 创建表,插入数据(dos窗口下,参照输入背景的指令)
- 项目中引入驱动包(新建项目,新建lib文件夹,然后lib文件夹下引入包(复制粘贴),然后add to build path)
步骤
- 加载数据库驱动
- 建立连接
- 创建执行SQL语句的Statement对象
- 从结果集取出数据
- 释放连接资源
JDBC的API
DriverManager
- 驱动管理类
DriverManager.registerDriver(new Driver());
- 使用上述方法注册驱动两次原因:当加载driver类(创建driver类),默认注册驱动,见下图
- 加载类——》静态代码块执行——》注册驱动
- 主机名+端口号+数据库名称
- 连接本机的MySQL时URL简写见上图
Connection
Statement
- 常用方法有两个(query,update)
- update后缀的方法返回值为影响的行数
- 批处理:多条SQL语句发送到数据库中执行
ResultSet
- 想象返回表格格式的数据
- 查询结果封装到结果集中
next方法
- 用于遍历
- 类比集合的iterator的使用,初始在第一行之前
交互资源释放
- 释放与数据库交互的对象(结果集,statement,connection)
- connection对象要早释放,晚创建
- 释放资源写在finally块中,最后赋值为null(要求外部声明,类比I/O)
- 要手动置为null,释放内存空间,不然没有垃圾回收
CRUD
未使用工具类
public class JDBCDemo1 {
@Test
/**
* JDBC资源的释放
*/
public void demo2(){
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 1.加载驱动
// DriverManager.registerDriver(new Driver());// 会导致驱动注册两次。
Class.forName("com.mysql.jdbc.Driver");
// 2.获得连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest", "root", "abc");
// 3.创建执行SQL语句的对象,并且执行SQL
// 3.1创建执行sql的对象
String sql = "select * from user";
stmt = conn.createStatement();
// 3.2执行sql
rs = stmt.executeQuery(sql);
while(rs.next()){
int uid = rs.getInt("uid");
String username = rs.getString("username");
String password = rs.getString("password");
String name = rs.getString("name");
System.out.println(uid+" "+username+" "+password+" "+name);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
// 4.释放资源
if (rs != null) {
try {
rs.close();
} catch (SQLException sqlEx) { // ignore
}
rs = null;
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;// 垃圾回收机制更早回收对象。
}
}
}
@Test
/**
* JDBC的入门程序
*/
public void demo1(){
try {
// 1.加载驱动
// DriverManager.registerDriver(new Driver());// 会导致驱动注册两次。
Class.forName("com.mysql.jdbc.Driver");
// 2.获得连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest", "root", "abc");
// 3.创建执行SQL语句的对象,并且执行SQL
// 3.1创建执行sql的对象
String sql = "select * from user";
Statement stmt = conn.createStatement();
// 3.2执行sql
ResultSet resultSet = stmt.executeQuery(sql);
while(resultSet.next()){
int uid = resultSet.getInt("uid");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
String name = resultSet.getString("name");
System.out.println(uid+" "+username+" "+password+" "+name);
}
// 4.释放资源
resultSet.close();
stmt.close();
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
使用工具类
public class JDBCDemo3 {
@Test
// 保存记录
public void demo1(){
Connection conn = null;
Statement stmt = null;
try{
// 获得连接:
conn = JDBCUtils.getConnection();
// 创建执行SQL语句的对象
stmt = conn.createStatement();
// 编写SQL:
String sql = "insert into user values (null,'ggg','123','小六')";
// 执行SQL:
int num = stmt.executeUpdate(sql);
if(num > 0){
System.out.println("保存成功!");
}
}catch(Exception e){
e.printStackTrace();
}finally{
// 释放资源:
JDBCUtils.release(stmt, conn);
}
}
}
一般步骤
- 注册驱动,获得连接
- 创建statement对象
- 编写SQL语句,执行SQL(executeUpdate)
- 关闭连接资源
增删改
- 与查找区别仅在于SQL语句
- 不需要结果集对象
- 增删改都使用 int executeUpdate(String)
- delete from +表 +where
- update 表名 set ----- where
- insert into 表名 values()
查询
- 使用结果集对象
- 与增删改相比,外加释放结果集资源
- if与while切换(查询一条记录与查询多条记录)
复用代码
封装如下三种方法:
- 注册驱动(参数为驱动类的全路径)
- 获取statement对象
- 重载的release方法
优化
原本各参数定死——》可变部分设置为静态常量,然后要给静态常量赋值——》加入静态代码块
再优化
- 将参数提取到配置文件中,然后程序中解析并读取相应参数
- 配置文件采用属性文件进行表示(键值对)
- src下新建属性文件
load方法的参数要为代表属性文件的输入流
-
方法1:使用inputstream,但存在问题:Java项目OK,但web项目发布到服务器中,没有src这一路径
-
方法2:使用类加载器加载并解析属性文件
SQL注入漏洞
- 登录成功与否实质是查询操作(能否查到)
- ‘’是SQL语句的一部分,将过滤条件的常量替换为String变量时,实质是替换单引号里面内容,"++"表示一个字符串的连接
- JDBCDemo4.login2(“aaa’ or '1=1”, “1fsdsdfsdf”);//由于and先运算,假设为false,但or后因为用户名正确,导致出现漏洞
- 由于–为注释
- 漏洞原因:输入了关键字
/**
* 演示JDBC的注入的漏洞
* @author jt
*
*/
public class JDBCDemo4 {
@Test
/**
* 测试SQL注入漏洞的方法
*/
public void demo1(){
boolean flag = JDBCDemo4.login2("aaa' or '1=1", "1fsdsdfsdf");
if(flag == true){
System.out.println("登录成功!");
}else{
System.out.println("登录失败!");
}
}
/**
* 避免SQL注入漏洞的方法
*/
public static boolean login2(String username,String password){
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
boolean flag = false;
try{
// 获得连接:
conn = JDBCUtils.getConnection();
// 编写SQL:
String sql = "select * from user where username = ? and password = ?";
// 预处理SQL:
pstmt = conn.prepareStatement(sql);
// 设置参数:
pstmt.setString(1, username);
pstmt.setString(2, password);
// 执行SQL:
rs = pstmt.executeQuery();
// 判断结果街
if(rs.next()){
flag = true;
}else{
flag = false;
}
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils.release(rs, pstmt, conn);
}
return flag;
}
/**
* 产生SQL注入漏洞的方法
* @param username
* @param password
* @return
*/
public static boolean login(String username,String password){
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
boolean flag = false;
try{
conn = JDBCUtils.getConnection();
// 创建执行SQL语句的对象:
stmt = conn.createStatement();
// 编写SQL:
String sql = "select * from user where username = '"+username+"' and password = '"+password+"'";
// 执行SQL:
rs = stmt.executeQuery(sql);
// 判断结果集中是否有数据。
if(rs.next()){
flag = true;
}else{
flag = false;
}
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils.release(rs, stmt, conn);
}
return flag;
}
}
解决
-
输入框进行GS校验(但hacker可以从地址栏输入,绕过校验)
-
分析:原本方式的SQL语句是用字符串来进行拼接(变量,例如username都是字符串的拼接)
-
正确的解决方法:使用占位符给SQL语句的变量处占位(则SQL语句格式固定)
-
保证把关键字当成普通字符串处理
-
只编译一次(因为用占位符进行占位了)
-
变量使用?占位
-
SQL语句已经预处理,不需要传入SQL语句了(execute方法)
PreparedStatement好处:
- 解决漏洞
- 相同,只编译一次
PreparedStatement
总结:
- 解决漏洞
- 传统方式,一条语句编译一次,而这种方式相同语句只编译一次(占位符占位了)
/**
* PreparedStatement的使用
* @author jt
*
*/
public class JDBCDemo5 {
@Test
/**
* 查询一条记录
*/
public void demo5(){
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try{
// 获得连接:
conn = JDBCUtils.getConnection();
// 编写SQL:
String sql = "select * from user where uid = ?";
// 预编译SQL:
pstmt = conn.prepareStatement(sql);
// 设置参数:
pstmt.setObject(1, 3);
// 执行SQL:
rs = pstmt.executeQuery();
// 判断结果集:
if(rs.next()){
System.out.println(rs.getInt("uid")+" "+rs.getString("username")+" "+rs.getString("password")+" "+rs.getString("name"));
//实际操作中可将一条记录存储在一个对象中,然后返回对象
}
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils.release(rs, pstmt, conn);
}
}
@Test
/**
* 查询所有数据
*/
public void demo4(){
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try{
// 获得连接:
conn = JDBCUtils.getConnection();
// 编写SQL:
String sql = "select * from user";
// 预编译SQL:
pstmt = conn.prepareStatement(sql);
// 设置参数
// 执行SQL:
rs = pstmt.executeQuery();
while(rs.next()){
System.out.println(rs.getInt("uid")+" "+rs.getString("username")+" "+rs.getString("password")+" "+rs.getString("name"));
}
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils.release(rs, pstmt, conn);
}
}
@Test
/**
* 删除数据
*/
public void demo3(){
Connection conn = null;
PreparedStatement pstmt = null;
try{
// 获得连接:
conn = JDBCUtils.getConnection();
// 编写SQL:
String sql = "delete from user where uid = ?";
// 预编译SQL:
pstmt = conn.prepareStatement(sql);
// 设置参数:
pstmt.setInt(1, 6);
// 执行SQL:
int num = pstmt.executeUpdate();
if(num > 0){
System.out.println("删除成功!");
}
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils.release(pstmt, conn);
}
}
@Test
/**
* 修改数据
*/
public void demo2(){
Connection conn = null;
PreparedStatement pstmt = null;
try{
// 获得连接:
conn = JDBCUtils.getConnection();
// 编写SQL:
String sql = "update user set username = ?,password = ?,name = ? where uid = ?";//变量用?替代
//有where过滤条件,不然所有记录修改
// 预编译SQL:
pstmt = conn.prepareStatement(sql);
// 设置参数:
pstmt.setString(1, "www");
pstmt.setString(2, "123456");
pstmt.setString(3, "张六");
pstmt.setInt(4, 6);
// 执行SQL:
int num = pstmt.executeUpdate();
if(num > 0){
System.out.println("修改成功!");
}
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils.release(pstmt, conn);
}
}
@Test
/**
* 保存数据
*/
public void demo1(){
Connection conn = null;
PreparedStatement pstmt = null;
try{
// 获得连接:
conn = JDBCUtils.getConnection();
// 编写SQL:
String sql = "insert into user values (null,?,?,?)";//null表示根据默认设置来,此处表的第一列为自增的主键列(已知条件)
// 预处理SQL:
pstmt = conn.prepareStatement(sql);
// 设置参数的值:
pstmt.setString(1, "qqq");
pstmt.setString(2, "123");
pstmt.setString(3, "张武");
// 执行SQL:
int num = pstmt.executeUpdate();
if(num > 0){
System.out.println("保存成功!");
}
}catch(Exception e){
e.printStackTrace();
}finally{
// 释放资源
JDBCUtils.release(pstmt, conn);
}
}
}
关键
- 设置参数时,setXXX为独有的方法,而setObject方法通吃(各种重载)
- 注意在前方SQL已预编译(预处理),所以executeUpdate方法无参数
- 变量用?替代
连接池
概述
- 创建和管理一个连接的缓冲池的技术
- 创建和销毁开销较大
- 连接池理解为一个装有许多连接的容器或者一块内存空间,里面存在许多连接
- 需要连接时,则获取连接/用完连接后归还给内存
- 上图:用户通过服务器端程序(servlet),最终调用到DAO(数据访问对象),然后创建连接和数据库交互
- 有了线程池,则初始时已创建好一个具有许多连接的容器(内存的一个地址)
C3P0连接池
- 许多开源连接池,仅以此为例
- 引入连接池的jar包(先添加到lib文件夹,然后右键,add to build path)
手动方式
-
没有配置文件,手动设置参数
- 创建连接池(主要实现类 ComboPooledDataSource),设置连接池参数(setDriverClass,setJdbcUrl,setUser,setPassword,setMaxPoolSize)
- 获得连接
- 编写与预编译SQL语句,设置参数
- 执行SQL语句
- 释放连接资源
-
调用的是:connection.close()方法,连接池内部已增强了该方法,销毁方法增强为归还方法
-
连接池的连接数最小默认为3,最大默认为15
@Test
/**
* 手动设置连接池
*/
public void demo1(){
// 获得连接:
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null; //例行操作
try{
// 创建连接池:
ComboPooledDataSource dataSource = new ComboPooledDataSource();
// 设置连接池的参数:
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql:///jdbctest");
dataSource.setUser("root");
dataSource.setPassword("abc");
dataSource.setMaxPoolSize(20);
dataSource.setInitialPoolSize(3);
// 获得连接:
conn = dataSource.getConnection();
// 编写Sql:
String sql = "select * from user";
// 预编译SQL:
pstmt = conn.prepareStatement(sql);
// 设置参数
// 执行SQL:
rs = pstmt.executeQuery();
while(rs.next()){
System.out.println(rs.getInt("uid")+" "+rs.getString("username")+" "+rs.getString("password")+" "+rs.getString("name"));
}
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils.release(rs, pstmt, conn); //封装的常用代码
}
}
配置文件方式
- 可使用properties文件或XML文件(XML配置是默认的,属性文件配置仍然被支持)
- 默认在类路径下查找为c3p0-config的XML文件——》创建c3p0-config.xml文件并放在src下
@Test
/**
* 使用配置文件的方式
*/
public void demo2(){
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try{
/*// 获得连接:
ComboPooledDataSource dataSource = new ComboPooledDataSource();*/
// 获得连接:
// conn = dataSource.getConnection();
conn = JDBCUtils2.getConnection();
// 编写Sql:
String sql = "select * from user";
// 预编译SQL:
pstmt = conn.prepareStatement(sql);
// 设置参数
// 执行SQL:
rs = pstmt.executeQuery();
while(rs.next()){
System.out.println(rs.getInt("uid")+" "+rs.getString("username")+" "+rs.getString("password")+" "+rs.getString("name"));
}
}catch(Exception e){
e.printStackTrace();
}finally{
JDBCUtils2.release(rs, pstmt, conn);
}
}
JDBC工具类
旧
/**
* JDBC的工具类
* @author jt
*
*/
public class JDBCUtils {
private static final String driverClass;
private static final String url;
private static final String username;
private static final String password;
static{
// 加载属性文件并解析:
Properties props = new Properties();
// 如何获得属性文件的输入流?
// 通常情况下使用类的加载器的方式进行获取:
InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
try {
props.load(is);
} catch (IOException e) {
e.printStackTrace();
}
driverClass = props.getProperty("driverClass");
url = props.getProperty("url");
username = props.getProperty("username");
password = props.getProperty("password");
}
/**
* 注册驱动的方法
* @throws ClassNotFoundException
*/
public static void loadDriver() throws ClassNotFoundException{
Class.forName(driverClass);
}
/**
* 获得连接的方法:
* @throws SQLException
*/
public static Connection getConnection() throws Exception{
loadDriver();
Connection conn = DriverManager.getConnection(url, username, password);
return conn;
}
/**
* 资源释放
*/
public static void release(Statement stmt,Connection conn){
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
public static void release(ResultSet rs,Statement stmt,Connection conn){
if(rs!= null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
}
新
- 定义静态常量,保证连接池只被初始化一次,不然以前写法的增删改查要创建四次连接池
/**
* JDBC的工具类
* @author jt
*
*/
public class JDBCUtils2 {
private static final ComboPooledDataSource dataSource = new ComboPooledDataSource();
/**
* 获得连接的方法:
* @throws SQLException
*/
public static Connection getConnection() throws Exception{
Connection conn = dataSource.getConnection();
return conn;
}
/**
* 资源释放
*/
public static void release(Statement stmt,Connection conn){
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
public static void release(ResultSet rs,Statement stmt,Connection conn){
if(rs!= null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
}
JDBC总结
反射机制
基本作用/常见方法
Class类
Constructor类
Field类
Method类
反射概述
机制
在运行期,动态调用类的对象的属性和方法
JVM负责class文件执行
JVM里的classloader把字节码文件加载到JVM中,然后JVM负责解释运行
JVM运行class文件称为运行期
可动态获取属性,方法,通过class对象动态生成类的实例
字节码文件加载到内存中产生Class对象——Class对象
构造——Constructor对象
反射常用对象
Person.class;//类
person.getClass;//对象
forName全类名:要进行异常处理
Constructor对象
getConstructor(可变参数)
参数类型为Class类型
0——》无参构造
newInstance参数对应构造器,要强转
Field对象
- 获得本类所有属性
- 获取类的public属性(包括父类)
关键反射私有属性(没有set/get方法)
Method对象
JUnit单元测试
- Junit是单元测试框架,本身也是单元测试的工具
- 主流开发模型:瀑布式,少量基于敏捷式开发
- 软件测试的单元测试由开发完成
新建Test Case类型的对象过程(该文件自动帮我们创建方法【空,要补充测试逻辑】)如图
- @Test注解表示Junit框架会调用方法内代码(类比main方法)
- 断言来显示结果
- 如果两个参数相等,通过
- @Ignore:跳过此方法
- 测试方法抛出异常
- 提供限制条件,timeout=1000(考虑程序效率时)
@before
@after
- 使用场景:创建连接(before)与关闭连接(after)定义为公共的资源(方法)
- 任何一个测试方法在执行时必须调用它(before为执行自己方法体之前,after为执行自己方法体之后)
@FixMethodOrder
- 多个test方法的执行顺序默认依赖于JVM的顺序,可手动指定测试方法的执行顺序,如上图
- 打包一起测试的实例
- 创建一个基于Test Case的测试类——》然后光标处list一起测试的场景,运行上述类则一起测试所有被list的类