目录
- JDBC的工作原理
- JDBC API:
- JDBC开发步骤
- 加载并注册JDBC驱动:
- 建立数据库连接:
- 创建Statement对象:
- 执行SQL语句:
- 处理结果:
- Connection接口的常用方法
- Statement接口的常用方法
- ResultSet接口的常用方法
- SQL注入
- 注入原理
- 防止SQL注入的方法
- PreparedStatement接口
- 使用
- 代码示例(部分)
- 代码封装+DAO模式
- DAO起着转换器的作用
- Properties类来读取配置文件
- 代码示例
- JDBC封装步骤
- 数据库连接池
- 程序中的连接池技术
- 连接池的工作原理
- 数据源
- 三层架构
- 分层的特点
- 分层原则
- 案例:使用分层开发实现登录的思路
- BaseDao
- DataSourceConfig
- 数据库连接池:DruidDataSourceConfig
- 统一响应模板
- 实体类
- 数据访问层——Dao层
- 业务逻辑层——service层
JDBC (Java DataBase Connectivity)是Java数据库连接技术的简称,提供连接各种常用数据库的能力
JDBC的工作原理
- SUN提供一套访问数据库的规范(就是一组接口),并提供连接数据库的协议标准
- 各个数据库厂商会提供一套API用来访问自己公司的数据库服务器,且API遵循SUN的规范
- JDBC是里面封装着操作各数据库的接口,而JDBC驱动才是接口的实现,没有驱动无法完成数据库连接!每个数据库厂商都有自己的驱动,用来连接自己公司的数据库。
JDBC API:
- DriverManager类 作用:管理各种不同的JDBC驱动
- Connection接口:负责连接数据库并担任传送数据的任务
- Statement接口:由 Connection 产生、负责发送执行SQL语句
- ResultSet接口:负责保存Statement执行后所产生的查询结果
JDBC开发步骤
加载并注册JDBC驱动:
- 这是建立数据库连接的第一步,我们需要先加载JDBC驱动,然后通过DriverManager的registerDriver方法进行注册。
先导入依赖:mysql-connector-java-5.1.46.jarClass.forName("com.mysql.jdbc.Driver");
建立数据库连接:
-
通过DriverManager的getConnection方法,我们可以建立与数据库的连接。
String url = "jdbc:mysql://127.0.0.1:3306/myschool?useSSL=false"; String userName = "root"; String password = "123456"; Connection conn = DriverManager.getConnection(url,userName,password);
创建Statement对象:
-
通过Connection对象的createStatement方法,我们可以创建一个Statement对象,用于执行SQL语句。
Statement stmt = conn.createStatement();
执行SQL语句:
-
通过Statement对象的executeQuery或executeUpdate方法,我们可以执行SQL语句,获取结果或者更新数据库。
String sql = "SELECT COUNT(1) FROM USER"; ResultSet rs = stmt.executeQuery(sql);
处理结果:
-
对于查询操作,我们需要处理ResultSet结果集;对于更新操作,我们不需要处理结果。
int userCount = 0; while (rs.next()){ //userCount = rs.getInt(1); userCount = rs.getInt("count(1)"); } System.out.println("该表中一共有" + userCount + "条记录!");
- 关闭资源:
-
最后,我们需要关闭打开的资源,包括ResultSet、Statement和Connection。
if(rs!=null){ rs.close(); } if(stmt!=null){ stmt.close(); } if(conn!=null){ conn.close(); }
Connection接口的常用方法
Statement接口的常用方法
ResultSet接口的常用方法
SQL注入
注入原理
注入原理:利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句
防止SQL注入的方法
- 过滤用户输入的数据中是否包含非法字符;
- 分步校验!先使用用户名来查询用户,如果查找到了,再比较密码;
- 使用 PreparedStatement 接口。
PreparedStatement接口
PreparedStatement接口是Statement的子接口,你可以使用该接口来替换Statement接口。
- 提高了代码的可读性和可维护性
- 提高了SQL语句执行的性能
- 提高了安全性
使用
- 使用Connection对象的prepareStatement(String sql):即创建它时就让它与一条SQL语句绑定;
- 编写SQL语句时,如果存在参数,使用“?”作为数据占位符;
- 调用PreparedStatement的setXXX()系列方法为占位符设置值,索引从1开始;
- 调用executeUpdate()或executeQuery()方法,但要注意,调用没有参数的方法;
代码示例(部分)
/**
* 用户登录
* @param userName 用户名
* @param userPass 用户密码
* @return
*/
public User toLogin(String userName,String userPass){
//1.获取连接对象
getConnection();
//2.编写SQL语句
String sql = "select ID,USERNAME,ROLE from user where userName = ? and userPass = ?";
System.out.println("要执行的SQL语句是:" + sql);
//3.创建statement对象
User user = null;
try {
ps = connection.prepareStatement(sql);
//3.1处理参数
ps.setString(1,userName);
ps.setString(2,userPass);
//4.执行并解析结果
resultSet = ps.executeQuery();
while (resultSet.next()){
user = new User();
user.setId(resultSet.getInt(1));
user.setUserName(resultSet.getString(2));
user.setRole(resultSet.getInt(3));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
closeResource();
}
return user;
}
代码封装+DAO模式
加载驱动、建立连接、关闭资源与业务无关。而业务中又要根据多个SQL的执行结果来进行业务处理,若代码全都放在一起,则:
- 可读性差
- 不利于后期修改和维护
- 不利于代码复用
采用面向接口编程,可以降低代码间的耦合性
- 隔离业务逻辑代码和数据访问代码
- 隔离不同数据库的实现
DAO起着转换器的作用
把实体类转换为数据库中的记录,dao层也被成为数据访问层
Properties类来读取配置文件
信息存储在MySQL数据库中,但在开发和部署时有可能使用不同的数据库,也可能因为客户的需求而更换数据库产品。数据库连接信息直接写死在java代码中会导致切换麻烦,因此可以将这类信息写在外部的配置文件中,这样切换配置/环境时,不需要重新编译程序。
让用户脱离程序本身修改相关的变量设置——使用配置文件
代码示例
private static String driverClassName;
private static String url;
private static String username;
private static String password;
static{
Properties properties = new Properties();
String path = "database.properties";
InputStream is = BaseDao.class.getClassLoader().getResourceAsStream(path);
try {
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
}
driverClassName = properties.getProperty("driverClassName");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
}
JDBC封装步骤
- 定义实体类传输数据;
- 将所有增删改查操作抽取成接口;
- 由不同数据库的实现类分别实现接口;
- 将通用的操作(打开、关闭连接、增、删、改、查等)封装到数据库工具类BaseDao的通用方法中。
数据库连接池
- 当一个用户要使用软件时,就需要频繁的使用数据库进行增删改查,每次增删改查操作都需要获取连接,频繁的连接导致系统的安全性和稳定性差。
- 如果说一个用户的频繁获取连接尚能接受,那么成千上万的用户进行增删改查来获取连接,对系统来说将是一个巨大的负荷!
- 我们可以设计一个用来存放连接的池子,这个池子起名为连接池。
- 连接池中存放一定数量的连接,当用户需要获取连接时,就从这个池子中获取,用完后关闭资源,再将连接放回连接池。
- 如此,一个连接就可以被反复使用,大大降低系统的压力解决了建立数据库连接耗费资源和时间很多的问题,提高了性能。
程序中的连接池技术
- 连接池技术的核心思想是:连接复用,通过建立一个数据库连接池以及一套连接使用、分配、管理策略,使得该连接池中的连接可以得到高效、安全的复用,避免了数据库连接频繁建立、关闭的开销。
- 由于对JDBC中的原始连接进行了封装,从而方便了数据库应用对于连接的使用(特别是对于事务处理),提高了获取数据库连接效率,也正是因为这个封装层的存在,隔离了应用的本身的处理逻辑和具体数据库访问逻辑,使应用本身的复用成为可能。
- 连接池主要由三部分组成:连接池的建立、连接池中连接的使用管理、连接池的关闭。
连接池的工作原理
连接池自动分配连接对象并对闲置的连接进行回收
数据源
- 连接池中的连接对象是由谁创建的呢——数据源
- javax.sql.DataSource接口负责建立与数据库的连接;
- 各个厂商需要让自己的连接池实现这个接口,缩减开发成本。
- 知名的连接池厂商:DBCP 、C3P0、Druid(阿里巴巴)
三层架构
表示层、业务逻辑层、数据访问层
分层的特点
- 分层将解决方案的组件分隔到不同的层中
- 在同一个层中组件之间保持内聚性
- 层与层之间保持松耦合
- 每一层都有自己的职责
- 上一层不用关心下一层的实现细节,上一层通过下一层提供的对外接口来使用其功能
- 上一层调用下一层的功能,下一层不能调用上一层功能
分层原则
-
封装性原则:每个层次向外公开接口,但是隐藏内部细节
比如:钥匙开锁,只知道锁提供的接口,但不知道锁的内部细节 -
顺序访问原则:下一层为上一层服务,但不使用上层的服务
比如:盖楼时需要先打地基,地基为上层建筑服务,但不使用上层的服务
分层结构中,不同层之间通过实体类传输数据
案例:使用分层开发实现登录的思路
- 表示层:设计用户登录界面,提醒用户输入用户名和密码,发送登录请求,调用业务逻辑层相关的接口,处理登录请求。
- 业务逻辑层:设计业务逻辑层的业务接口,设计业务接口的实现类,在相应的方法中调用数据访问层的接口,处理数据访问层返回的数据,将处理的结果返回给表示层。
- 数据访问层:设计数据访问层的接口,设计接口的实现类,执行查询操作,返回数据给业务层。
BaseDao
package com.myschool.day05.base;
import com.myschool.day05.config.DataSourceConfig;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.List;
import java.util.Properties;
/**
* @author: zjl
* @datetime: 2024/1/13
* @desc:
*/
public abstract class BaseDao {
private static DataSourceConfig dataSourceConfig = DataSourceConfig.getInstance();
private static String driverClassName = dataSourceConfig.getValue("driverClassName");
private static String url = dataSourceConfig.getValue("url");
private static String username = dataSourceConfig.getValue("username");
private static String password = dataSourceConfig.getValue("password");
private PreparedStatement ps;
public ResultSet rs;
//1.加载驱动、创建数据库连接对象
public static Connection getConn(){
Connection conn = null;
try {
Class.forName(driverClassName);
conn = DriverManager.getConnection(url,username,password);
conn.setAutoCommit(false);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
//2.封装增删改的通用方法
public int executeUpdate(Connection conn, String sql, List<Object> params) throws SQLException {
int line = 0;
try {
ps = conn.prepareStatement(sql);
if(params!=null && !params.isEmpty()){
for (int i = 0; i < params.size(); i++) {
ps.setObject((i + 1),params.get(i));
}
}
line = ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
close();
}
return line;
}
//3.封装查询的通用方法
public ResultSet executeQuery(Connection conn, String sql, List<Object> params) throws SQLException {
ps = conn.prepareStatement(sql);
if(params!=null && !params.isEmpty()){
for (int i = 0; i < params.size(); i++) {
ps.setObject((i + 1),params.get(i));
}
}
rs = ps.executeQuery();
return rs;
}
//4.关闭链接
public void close() throws SQLException {
if(rs!=null){
rs.close();
}
if(ps!=null){
ps.close();
}
}
}
DataSourceConfig
package com.myschool.day05.config;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* @author: zjl
* @datetime: 2024/1/13
* @desc:
*/
public class DataSourceConfig {
private static DataSourceConfig dataSourceConfig;
private Properties properties;
private DataSourceConfig(){
properties = new Properties();
String path = "database.properties";
InputStream is = DataSourceConfig.class.getClassLoader().getResourceAsStream(path);
try {
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
}
}
public static DataSourceConfig getInstance(){
if(dataSourceConfig == null){
synchronized (DataSourceConfig.class){
if(dataSourceConfig == null){
dataSourceConfig = new DataSourceConfig();
}
}
}
return dataSourceConfig;
}
public String getValue(String key){
return properties.getProperty(key);
}
}
数据库连接池:DruidDataSourceConfig
package com.myschool.day05.config;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.util.Properties;
/**
* @author: zjl
* @datetime: 2024/1/16
* @desc:
*/
public class DruidDataSourceConfig {
private static DruidDataSourceConfig dataSourceConfig;
private Properties properties;
private static DataSource dataSource;
public DruidDataSourceConfig(){
properties = new Properties();
try {
properties.load(DruidDataSourceConfig.class.getClassLoader().getResourceAsStream("database.properties"));
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
public static DruidDataSourceConfig getInstance(){
if(dataSourceConfig == null){
synchronized (DruidDataSourceConfig.class){
if (dataSourceConfig == null) {
dataSourceConfig = new DruidDataSourceConfig();
}
}
}
return dataSourceConfig;
}
public Connection getConnection() {
try {
Connection connection = dataSource.getConnection();
connection.setAutoCommit(false);
return connection;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
统一响应模板
package com.myschool.day05.vo;
/**
* @author: zjl
* @datetime: 2024/1/13
* @desc:
*/
public class ResponseResult<T> {
//private boolean success;//响应状态
private int code;//响应码
private String msg;//响应信息
private T data;//响应数据
//有响应数据的模板方法
public ResponseResult(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
//无响应数据的模板方法
public ResponseResult(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
实体类
package com.myschool.day05.pojo;
/**
* @author: zjl
* @datetime: 2024/1/10
* @desc:
*/
public class User {
private int id;
private String userName;
private String userPass;
private int role;
public User(int id, String userName, String userPass, int role) {
this.id = id;
this.userName = userName;
this.userPass = userPass;
this.role = role;
}
public User() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserPass() {
return userPass;
}
public void setUserPass(String userPass) {
this.userPass = userPass;
}
public int getRole() {
return role;
}
public void setRole(int role) {
this.role = role;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", userName='" + userName + '\'' +
", userPass='" + userPass + '\'' +
", role=" + role +
'}';
}
}
数据访问层——Dao层
package com.myschool.day05.dao;
import com.myschool.day05.pojo.User;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
public interface UserDao {
User selectUserByUserNameAndUserPass(Connection conn,String userName, String userPass) throws SQLException;
List<User> selectUserByUserNameAndRole(Connection conn,String keyWords,int role) throws SQLException;
int updateUserPassById(Connection conn,User user) throws SQLException;
}
package com.myschool.day05.dao.impl;
import com.myschool.day05.base.BaseDao;
import com.myschool.day05.dao.UserDao;
import com.myschool.day05.pojo.User;
import com.mysql.jdbc.StringUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author: zjl
* @datetime: 2024/1/13
* @desc:
*/
public class UserDaoImpl extends BaseDao implements UserDao {
@Override
public User selectUserByUserNameAndUserPass(Connection conn, String userName, String userPass) throws SQLException {
String sql = "SELECT ID,USERNAME,ROLE FROM USER WHERE USERNAME = ? AND USERPASS=?";
List<Object> params = new ArrayList<>();
Collections.addAll(params,userName,userPass);
User user = null;
try {
rs = executeQuery(conn,sql,params);
while (rs.next()){
user = new User();
user.setId(rs.getInt(1));
user.setUserName(rs.getString(2));
user.setRole(rs.getInt(3));
}
}finally {
this.close();
}
return user;
}
/**
* 如果 keyWords为null,role==0 表示查询全部
* 如果 keyWords为null,role!=0 表示按照role查询
* 如果 keyWords不为null,role==0 表示按照userName模糊查询
* 如果 keyWords不为null,role!=0 表示按照userName模糊查询并且按照role查询
* @param conn
* @param keyWords
* @param role
* @return
* @throws SQLException
*/
@Override
public List<User> selectUserByUserNameAndRole(Connection conn,String keyWords,int role) throws SQLException {
//String sql = "SELECT * FROM USER WHERE 1=1";
StringBuffer sbf = new StringBuffer("SELECT * FROM USER WHERE 1=1");
List<User> userList = new ArrayList<>();
try {
List<Object> params = new ArrayList<>();
//if(keyWords!=null && keyWords.trim().length()>0){
if(!StringUtils.isNullOrEmpty(keyWords)){
sbf.append(" AND USERNAME LIKE CONCAT('%',?,'%') ");
params.add(keyWords);
}
if(role != 0){
sbf.append(" AND ROLE = ?");
params.add(role);
}
System.out.println("动态的SQL语句是:" + sbf.toString());
rs = executeQuery(conn,sbf.toString(),params);
User user = null;
while (rs.next()){
user = new User();
user.setId(rs.getInt(1));
user.setUserName(rs.getString(2));
user.setUserPass(rs.getString(3));
user.setRole(rs.getInt(4));
userList.add(user);
}
}finally {
close();
}
return userList;
}
@Override
public int updateUserPassById(Connection conn,User user) throws SQLException {
String sql = "UPDATE USER SET USERPASS=? WHERE ID=?";
List<Object> params = new ArrayList<>();
Collections.addAll(params,user.getUserPass(),user.getId());
int line = executeUpdate(conn,sql,params);
return line;
}
}
业务逻辑层——service层
package com.myschool.day05.service;
import com.myschool.day05.pojo.User;
import com.myschool.day05.vo.ResponseResult;
import java.util.List;
public interface UserService {
/**
* 用户登录的业务
* @param userName
* @param userPass
* @return
*/
ResponseResult<User> login(String userName,String userPass);
ResponseResult<List<User>> getUserList(String keyWords,int role);
ResponseResult modifyPassword(User user);
}
package com.myschool.day05.service.impl;
import com.myschool.day05.base.BaseDao;
import com.myschool.day05.config.DruidDataSourceConfig;
import com.myschool.day05.dao.UserDao;
import com.myschool.day05.dao.impl.UserDaoImpl;
import com.myschool.day05.pojo.User;
import com.myschool.day05.service.UserService;
import com.myschool.day05.vo.ResponseResult;
import org.apache.log4j.Logger;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
/**
* @author: zjl
* @datetime: 2024/1/13
* @desc:
*/
public class UserServiceImpl implements UserService {
Logger log = Logger.getLogger(UserServiceImpl.class);
UserDao userDao = new UserDaoImpl();
@Override
public ResponseResult<User> login(String userName, String userPass) {
log.info("登录请求:"+userName+" "+userPass);
//Connection conn = BaseDao.getConn();
Connection conn = DruidDataSourceConfig.getInstance().getConnection();
try {
User user = userDao.selectUserByUserNameAndUserPass(conn,userName,userPass);
//System.out.println(5/0);
log.info("登录信息是:" + user);
if(user!=null){
return new ResponseResult<>(200,"登录成功!",user);
}
return new ResponseResult<>(0,"登录失败!");
}catch (Exception e){
log.error("程序异常,异常信息为:" + e);
e.printStackTrace();
return new ResponseResult<>(500,"程序错误!");
}finally {
try {
if (conn!=null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@Override
public ResponseResult<List<User>> getUserList(String keyWords,int role) {
Connection conn = BaseDao.getConn();
try {
List<User> userList = userDao.selectUserByUserNameAndRole(conn,keyWords,role);
if(userList!=null && !userList.isEmpty()){
return new ResponseResult<>(200,"查询用户列表成功!",userList);
}
return new ResponseResult<>(0,"查询用户列表失败!");
}catch (Exception e){
e.printStackTrace();
}finally {
try {
if (conn!=null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return new ResponseResult<>(500,"程序异常!");
}
@Override
public ResponseResult modifyPassword(User user) {
Connection conn = BaseDao.getConn();
try {
int line = userDao.updateUserPassById(conn,user);
if (line > 0){
conn.commit();
return new ResponseResult(200,"密码修改成功!请重新登陆!");
}
return new ResponseResult(0,"密码修改失败!请检查数据!");
}catch (Exception e){
e.printStackTrace();
}finally {
try {
if (conn!=null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
try {
conn.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
return new ResponseResult(500,"程序错误!");
}
}