什么是数据访问类
数据访问类(Data Access Class,常对应DAO模式中的DAO类)是专门用于处理应用程序与数据存储层交互的封装类,核心作用是把数据库的连接、查询、更新、关闭等操作全部封装在类内部,让上层业务代码不需要直接编写SQL语句或者处理数据库连接细节,只需要调用数据访问类提供的公开方法即可完成数据操作。

在分层架构中,数据访问类通常位于数据访问层(DAO层),上层是业务逻辑层,下层直接对接数据库或者ORM框架。这种设计可以让业务逻辑和数据操作逻辑完全解耦,当数据库类型变更或者SQL逻辑调整时,只需要修改数据访问类的实现,不需要改动业务层的代码,大幅降低了代码的维护成本。
数据访问类的核心特性
- 封装性:隐藏数据库连接、SQL执行、结果集解析等底层细节,对外只暴露符合业务语义的方法
- 复用性:相同的数据操作逻辑可以封装成通用方法,在多个业务场景中重复调用
- 可测试性:可以通过模拟数据访问类的行为,在不依赖真实数据库的情况下完成业务层单元测试
- 单一职责:每个数据访问类通常只对应一张数据库表或者一个独立的数据实体,只负责该实体的相关操作
数据访问类实例:Java实现用户数据访问类
下面以Java语言为例,实现一个操作用户表(user)的数据访问类,用户表包含id、username、password三个字段,数据访问类提供新增用户、根据id查询用户、更新用户密码三个核心方法。
import java.sql.*;
// 用户实体类
class User {
private int id;
private String username;
private String password;
public User() {}
public User(int id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
// getter和setter方法
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 getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
// 用户数据访问类
public class UserDao {
// 数据库连接信息,实际开发中通常放在配置文件中
private static final String URL = "jdbc:mysql://127.0.0.1:3306/test_db?useSSL=false";
private static final String USER = "root";
private static final String PASSWORD = "123456";
// 获取数据库连接
private Connection getConnection() throws SQLException {
return DriverManager.getConnection(URL, USER, PASSWORD);
}
// 新增用户
public void addUser(User user) {
String sql = "INSERT INTO user (username, password) VALUES (?, ?)";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
pstmt.setString(1, user.getUsername());
pstmt.setString(2, user.getPassword());
pstmt.executeUpdate();
// 获取自增主键
try (ResultSet rs = pstmt.getGeneratedKeys()) {
if (rs.next()) {
user.setId(rs.getInt(1));
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
// 根据id查询用户
public User getUserById(int id) {
String sql = "SELECT id, username, password FROM user WHERE id = ?";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, id);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
User user = new User();
user.setId(rs.getInt("id"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
return user;
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
// 更新用户密码
public boolean updateUserPassword(int id, String newPassword) {
String sql = "UPDATE user SET password = ? WHERE id = ?";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, newPassword);
pstmt.setInt(2, id);
return pstmt.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
// 测试入口
public static void main(String[] args) {
UserDao userDao = new UserDao();
// 新增用户测试
User newUser = new User();
newUser.setUsername("test_user");
newUser.setPassword("test123");
userDao.addUser(newUser);
System.out.println("新增用户id:" + newUser.getId());
// 查询用户测试
User queryUser = userDao.getUserById(newUser.getId());
if (queryUser != null) {
System.out.println("查询到的用户名:" + queryUser.getUsername());
}
// 更新密码测试
boolean updateResult = userDao.updateUserPassword(newUser.getId(), "new_test123");
System.out.println("密码更新结果:" + updateResult);
}
}
数据访问类实例:Python实现文章数据访问类
下面以Python语言为例,实现一个操作文章表(article)的数据访问类,文章表包含id、title、content、create_time四个字段,数据访问类提供新增文章、查询所有文章、根据id删除文章三个核心方法,使用sqlite3作为数据库示例。
import sqlite3
from datetime import datetime
# 文章实体类
class Article:
def __init__(self, id=None, title=None, content=None, create_time=None):
self.id = id
self.title = title
self.content = content
self.create_time = create_time
# 文章数据访问类
class ArticleDao:
def __init__(self, db_path="test.db"):
self.db_path = db_path
self._init_table()
# 初始化表结构
def _init_table(self):
create_sql = """
CREATE TABLE IF NOT EXISTS article (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT,
create_time TEXT NOT NULL
)
"""
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute(create_sql)
conn.commit()
# 获取数据库连接
def _get_connection(self):
return sqlite3.connect(self.db_path)
# 新增文章
def add_article(self, article):
sql = "INSERT INTO article (title, content, create_time) VALUES (?, ?, ?)"
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute(sql, (article.title, article.content, datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
conn.commit()
article.id = cursor.lastrowid
return article
# 查询所有文章
def get_all_articles(self):
sql = "SELECT id, title, content, create_time FROM article"
articles = []
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute(sql)
rows = cursor.fetchall()
for row in rows:
article = Article(
id=row[0],
title=row[1],
content=row[2],
create_time=row[3]
)
articles.append(article)
return articles
# 根据id删除文章
def delete_article_by_id(self, article_id):
sql = "DELETE FROM article WHERE id = ?"
with self._get_connection() as conn:
cursor = conn.cursor()
cursor.execute(sql, (article_id,))
conn.commit()
return cursor.rowcount > 0
# 测试入口
if __name__ == "__main__":
article_dao = ArticleDao()
# 新增文章测试
new_article = Article(title="测试文章", content="这是一篇测试文章内容")
article_dao.add_article(new_article)
print(f"新增文章id:{new_article.id}")
# 查询所有文章测试
all_articles = article_dao.get_all_articles()
print(f"当前文章总数:{len(all_articles)}")
for art in all_articles:
print(f"文章标题:{art.title}")
# 删除文章测试
delete_result = article_dao.delete_article_by_id(new_article.id)
print(f"文章删除结果:{delete_result}")
数据访问类编写注意事项
- 不要在数据访问类中编写业务逻辑,数据访问类只负责数据操作,所有业务判断和计算都应该放在业务逻辑层
- 合理处理数据库资源,尽量使用try-with-resources(Java)或者上下文管理器(Python)自动关闭连接、语句和结果集,避免资源泄露
- SQL语句尽量使用预编译语句,避免SQL注入风险,不要直接拼接字符串生成SQL
- 数据访问类的公开方法命名要符合业务语义,比如<get_user_by_id>而不是<execute_query>,提升代码可读性
- 对于复杂的查询条件,可以封装成查询参数类传递给数据访问类,避免方法参数过多
总结
数据访问类是分层架构中不可或缺的基础组件,通过封装数据操作细节,实现了业务逻辑和数据逻辑的解耦,让代码更易维护、更易扩展。不同语言的数据访问类实现细节有所差异,但核心设计思想是一致的,开发者可以根据实际使用的技术栈,参考上述实例调整实现方式,同时注意遵循数据访问类的编写规范,避免常见的问题。