JDBC(JAVA访问数据库解决方案)
-
JDBC定义一套标准接口,即访问数据库的通用API,不同数据库根据特点去实现这些接口
-
JDBC的工作过程
- 加载驱动,建立连接
- 创建语句对象
- 执行SQL语句
- 处理结果集
- 关闭连接(切记!!) -
JDBC的使用步骤
1. 导入JDBC驱动jar
2. 注册JDBC驱动Class.forName("驱动程序类名")
-
获得Connection对象
- 连接到数据库 - 三个参数 URL、username,password - DriverManager.getConnection(url, user, password)
-
创建Statement(语句对象)
- conn.creatStatement() 创建对象 用于执行SQL语句 - execute()可以执行任何SQL语句常用于执行DDL - executeUpdate()用来执行 insert update delect语句 - executeQuerry 常用来执行SELECT语句
-
处理SQL执行结果
- execute() 无异常则执行成功 - executeUpdate 返回数字,表示更新“行”的数量 - executeQuerry 返回ResultSet(结果集)代表对象的二维查询结果,使用while循环遍历处理,失败则抛出异常
-
关闭数据库连接(非常重要,切记)
- conn.close()
Class.forName("com.mysql.cj.jdbc.Driver");
//加载驱动
//System.out.println("OK");
String url = "jdbc:mysql://localhost:3306/user? ";
String user = "root";
String password = "123456";
//getConnection用来连接到数据库,如果不成功将出现异常
Connection conn = DriverManager.getConnection(url, user, password);
//输出引用conn对象的实际类型
//证明驱动程序提供了Connection接口的实现类
System.out.println(conn.getClass());
//创建Statement语句对象
Statement st = conn.createStatement();
//执行SQL
String ddl = "create table robin_dome"
+ "(id INT(7),"+
"name VARCHAR(100))";
//String ddl ="drop table robin_dome";
boolean b = st.execute(ddl);//创建删除表,使用execute方法
//返回true表示有结果集,flase表示没有结果集,创建失败抛出异常
// 没有异常表示创建成功
System.out.println(b);
conn.close();
ResultSet
- ResultSet代表DQL查询出来的结果,是二维结果,其内部维护了一个读取数据的游标。默认情况游标第一行数据之前,当调用next()方法时,游标会向下移动,并将返回结果集中是否包含数据,如果包含数据则返回true,结果集中还提供了很多的getXXX()方法用于获取指向当前结果集中的数据
String sql = "SELECT * FROM robin_dome";
ResultSet rs = st.executeQuery(sql);
while(rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
System.out.println(id+",");
} // SELECT语句使用executeQuery()查询,while循环输出
Properties 的使用
-
Properties时java中专门用来读取配置文件的API(java中的配置文件都以propertie结尾)
-
利用配置文件可以将程序中的参数都保存到配置文件中,修改程序只需要修改配置文件即可
- 其底层就是文本文件IO - Properties本身实现Map接口,内部是散列表 - Properties限定了key和value都是String类型
-
Properties常用的API方法
- load(流) 读取一个配置文件 - String getProperties(key) 读取一个属性值
-
使用步骤
1.创建Properties对象 2.利用load方法读取配置文件 3. 利用getProperties读取参数
Properties cfg = new Properties();
InputStream in = DbUtil.class.getClassLoader().getResourceAsStream("db.properties");
//用来读取配置文件,当不是在某一文件夹下而是在代码平级目录下时使用该方法读取配置文件
try {
cfg.load(in);
System.out.println(cfg);
driver = cfg.getProperty("jdbc.driver");
url = cfg.getProperty("jdbc.url");
user = cfg.getProperty("jdbc.user");
password = cfg.getProperty("jdbc.password");
} catch (IOException e) {
e.printStackTrace();
}
管理数据库连接
-
在软件中对数据库的连接使用非常频繁,如果每次都创建连接,就会造成代码大量冗余,常规做法是建立数据库工具类
-
实现步骤
1 .创建数据库连接参数文件 db.properties 2. 创建DbUtil.java封装数据库连接方法 - 利用Properties读取配置文件中的数据库连接参数 - 创建方法 封装数据库连接过程 3.使用方法
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class DbUtil {
/*
* 将这四个变量放在类中是为了减少生成对象
* static类只此一份,不用再每一次调用生成对象
*/
static String driver;
static String url;
static String user;
static String password;
//读取文件中的数据库连接参数
static {
//初始化静态属性
//1.利用properties读取配置文件
//2.从配置文件中查找相应参数
Properties cfg = new Properties();
InputStream in = DbUtil.class.getClassLoader().getResourceAsStream("db.properties");
try {
cfg.load(in);
System.out.println(cfg);
driver = cfg.getProperty("jdbc.driver");
url = cfg.getProperty("jdbc.url");
user = cfg.getProperty("jdbc.user");
password = cfg.getProperty("jdbc.password");
} catch (IOException e) {
e.printStackTrace();
}
}
/*
* 封装数据库连接过程
* 简化数据库连接
*/
public static Connection getConnection() throws Exception {
Class.forName(driver);
Connection conn = DriverManager.getConnection(url, user, password);
return conn;
}
/*
* 关闭数据库连接的方法,封装复杂的关闭过程
*/
public static void close(Connection conn) {
if(conn!=null) {
try {
conn.close();
}catch(Exception e) {
e.printStackTrace();
}
}
}
}
数据库连接池(管理并发访问数据库连接的理想解决方案)
-
连接池是创建和管理连接的缓冲池技术,将连接准备好被任何需要他们的应用使用
-
连接池多种多样,基本都能实现方法互通,DBCP只是其中一种
-
DriverManager管理数据库连接适合单线程使用,在多线程并发情况下为了能够重用数据库连接,同时控制并发连接总数,保护数据库,避免练过载,一定要使用数据库连接池
-
Properties常用API
setInitialSize 设置初始化数据库连接数 setMaxActive 设置最大连接数 setDriverClassName(driver); 配置驱动 setUrl(url) 配置Url setUsername(username) 配置用户名 setPassword(password) 配置密码 getConnection() 获取空闲连接
-
使用步骤
1.Maven导入DBCP包 <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.4</version> </dependency> 2.通过上述方法设置参数 3.getConnection() 获取连接
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import org.apache.commons.dbcp.BasicDataSource;
/*
*连接池版数据库连接类管理工具
*适合于并发场合
*/
public class DBUtil {
private static String driver;
private static String url;
private static String username;
private static String password;
private static int initSize;
private static int maxAction;
private static BasicDataSource ds;
static {
ds = new BasicDataSource();
Properties cfg = new Properties();
try {
InputStream in = DBUtil.class.getClassLoader().getResourceAsStream("db.properties");
cfg.load(in);
driver = cfg.getProperty("jdbc.driver");
url = cfg.getProperty("jdbc.url");
username = cfg.getProperty("jdbc.user");
password = cfg.getProperty("jdbc.password");
initSize =Integer.parseInt(cfg.getProperty("initSize"));
maxAction= Integer.parseInt(cfg.getProperty("maxAction"));
in.close();
//初始化连接池
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
ds.setInitialSize(initSize);
ds.setMaxActive(maxAction);
}catch(Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() {
/*
* getConnection()从连接池中获取重用的连接,如果连接池满了
* 则等待,如果有链接归还,则获取重用链接
*/
try {
Connection conn = ds.getConnection();
return conn;
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException();
}
}
public static void Close(Connection conn) {
//将用完的连接归还到连接池
if(conn!=null) {
try{
conn.close();
}catch(Exception e) {
e.printStackTrace();
}
}
}
}
PreparedStatement以及注入攻击
-
注入攻击
注入攻击: 用户输入了 含有SQL成分的参数,参数在拼接时造成SQL语义的改变 改变SQL语义的执行计划,最终的执行结果就完全改变
-
注入攻击一个例子
public static boolean login(String name,String pwd) {
String sql = "select count(*) as c from user "
+ "where name =\'"+name+"\' "+"and pwd =\'"+pwd+"\'";
//System.out.println(sql);
Connection conn = null;
try {
conn = DBUtil.getConnection();
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sql);
while(rs.next()) {
int n = rs.getInt("c");
return n>=1;
}
}catch(Exception e) {
e.printStackTrace();
}finally {
DBUtil.Close(conn);
}
return false;
}
当用户密码输入 1' or '1' = 1' 时无视账号密码直接进入数据库
-
为了避免注入攻击
1.拦截用户输入的SQL成分 2. 固定执行计划,避免改变执行逻辑 - 使用prepareStatement
-
prepareStatement使用的一些说明
SQL语句再发送到数据库时会将SQL语句编译为执行计划,数据库通过执行计划来进行数据库操作。 当数据库语句有任何变动时,哪怕是一个空格都会让数据库重新编译执行计划。 为了减少不必要的资源浪费在使用中一般使用prepareStatement进行预编译,而且能够避免注入攻击
public static boolean login1(String name,String pwd) {
String sql = "select count(*) as c from user where name =? and pwd=?";
Connection conn = null;
try {
conn = DBUtil.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, name);
ps.setString(2, pwd);
ResultSet rs = ps.executeQuery();
while(rs.next()) {
int n = rs.getInt("c");
return n>=1;
}
}catch(Exception e) {
e.printStackTrace();
}finally {
DBUtil.Close(conn);
}
return false;
}