










JPA、MyBatis、JDBC、MyBatis-Plus 的差异与操作说明
在 Java 后端开发中,数据访问层的技术选型直接影响开发效率与维护成本。下面从 JDBC、MyBatis、MyBatis-Plus、JPA 四种方案出发,对比它们的定位、差异,并给出各自的代码示例。
1. JDBC(Java Database Connectivity)
JDBC 是 Java 访问数据库的最底层 API,所有 ORM 框架底层最终都基于 JDBC 完成数据库交互。它的特点是灵活、可控,但需要开发者手动处理连接、预编译、结果集映射和异常关闭等大量样板代码。
java
// 原生 JDBC 示例
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "123456";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement ps = conn.prepareStatement("SELECT * FROM user WHERE id = ?")) {
ps.setLong(1, 1L);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getString("name"));
}
}
} catch (SQLException e) {
e.printStackTrace();
}
特点:性能最高、最灵活,但代码冗余、易出错,适合对 SQL 有极致控制需求的场景。
2. MyBatis
MyBatis 是半自动 ORM 框架,SQL 由开发者编写,框架负责参数映射与结果集映射。它保留了 SQL 的灵活性,同时大幅减少了 JDBC 样板代码。
java
// Mapper 接口
public interface UserMapper {
User selectById(@Param("id") Long id);
}
xml
<!-- UserMapper.xml -->
<select id="selectById" resultType="com.example.User">
SELECT * FROM user WHERE id = #{id}
</select>
java
// 调用
User user = userMapper.selectById(1L);
特点:SQL 完全可控,适合复杂查询、多表关联、动态 SQL 场景;但需要手写 XML 或注解 SQL,开发量相对较大。
3. MyBatis-Plus
MyBatis-Plus 是 MyBatis 的增强工具,在保留 MyBatis 全部能力的基础上,提供了通用 CRUD、条件构造器、分页插件等开箱即用的能力,大幅减少重复 SQL 编写。
java
// 实体类
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
}
java
// Mapper 继承 BaseMapper,直接获得通用 CRUD
public interface UserMapper extends BaseMapper<User> {
}
java
// 使用条件构造器查询
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getName, "张三").gt(User::getAge, 18);
List<User> users = userMapper.selectList(wrapper);
// 分页查询
Page<User> page = userMapper.selectPage(new Page<>(1, 10), wrapper);
特点:单表 CRUD 几乎零 SQL,开发效率极高;复杂 SQL 仍可手写,兼顾效率与灵活性,是目前国内最流行的 MyBatis 增强方案。
4. JPA(Java Persistence API)
JPA 是 Java 官方规范,Hibernate 是其最主流的实现。JPA 是全自动 ORM,开发者通过实体注解和接口方法命名约定即可完成数据操作,无需编写 SQL。
java
// 实体类
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Integer age;
}
java
// Repository 接口,继承 JpaRepository 获得通用 CRUD
public interface UserRepository extends JpaRepository<User, Long> {
// 方法命名约定自动生成查询
List<User> findByNameAndAgeGreaterThan(String name, Integer age);
}
java
// 调用
List<User> users = userRepository.findByNameAndAgeGreaterThan("张三", 18);
特点:全自动映射,开发效率高,跨数据库移植性好;但复杂 SQL 和动态查询控制力较弱,性能调优相对困难,学习曲线较陡。
5. 四种方案对比总结
| 维度 | JDBC | MyBatis | MyBatis-Plus | JPA |
|---|---|---|---|---|
| 自动化程度 | 无 | 半自动 | 半自动(增强) | 全自动 |
| SQL 控制力 | 完全控制 | 完全控制 | 完全控制 | 较弱 |
| 开发效率 | 低 | 中 | 高 | 高 |
| 学习成本 | 低 | 中 | 中 | 较高 |
| 复杂查询 | 手写 SQL | 手写 SQL | 手写 SQL + 条件构造器 | 较难 |
| 典型场景 | 底层框架/极致性能 | 复杂业务 SQL | 快速 CRUD + 复杂 SQL | 标准规范/快速开发 |
选型建议:追求极致 SQL 控制选 JDBC;复杂查询多选 MyBatis;国内快速开发首选 MyBatis-Plus;追求规范统一、跨库移植选 JPA。











6. 常见查询操作示例
下面补充几种最常见的用户查询场景,分别用 JDBC、MyBatis、MyBatis-Plus、JPA 四种方式实现,方便对比它们的写法差异。
6.1 根据 id 查询用户
JDBC
java
// 根据 id 查询用户
String sql = "SELECT * FROM user WHERE id = ?";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setLong(1, 1L);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
User u = new User();
u.setId(rs.getLong("id"));
u.setName(rs.getString("name"));
u.setAge(rs.getInt("age"));
System.out.println(u);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
MyBatis
java
// Mapper 接口
public interface UserMapper {
User selectById(@Param("id") Long id);
}
xml
<!-- UserMapper.xml -->
<select id="selectById" resultType="com.example.User">
SELECT * FROM user WHERE id = #{id}
</select>
java
// 调用
User user = userMapper.selectById(1L);
MyBatis-Plus
java
// 直接使用 BaseMapper 内置方法
User user = userMapper.selectById(1L);
JPA
java
// Repository 接口
public interface UserRepository extends JpaRepository<User, Long> {
}
// 调用
User user = userRepository.findById(1L).orElse(null);
6.2 根据用户名模糊查询用户
JDBC
java
// 根据用户名模糊查询
String sql = "SELECT * FROM user WHERE name LIKE ?";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, "%张%");
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getString("name"));
}
}
} catch (SQLException e) {
e.printStackTrace();
}
MyBatis
java
// Mapper 接口
public interface UserMapper {
List<User> selectByNameLike(@Param("name") String name);
}
xml
<!-- UserMapper.xml -->
<select id="selectByNameLike" resultType="com.example.User">
SELECT * FROM user WHERE name LIKE CONCAT('%', #{name}, '%')
</select>
java
// 调用
List<User> users = userMapper.selectByNameLike("张");
MyBatis-Plus
java
// 使用条件构造器模糊查询
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(User::getName, "张");
List<User> users = userMapper.selectList(wrapper);
JPA
java
// Repository 接口
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByNameContaining(String name);
}
// 调用
List<User> users = userRepository.findByNameContaining("张");
6.3 根据 id、用户名、密码三个条件一起查询(用户名和密码支持模糊查询)
JDBC
java
// 多条件组合查询,用户名和密码模糊匹配
String sql = "SELECT * FROM user WHERE id = ? AND name LIKE ? AND password LIKE ?";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setLong(1, 1L);
ps.setString(2, "%张%");
ps.setString(3, "%123%");
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getString("name"));
}
}
} catch (SQLException e) {
e.printStackTrace();
}
MyBatis
java
// Mapper 接口
public interface UserMapper {
List<User> selectByCondition(@Param("id") Long id,
@Param("name") String name,
@Param("password") String password);
}
xml
<!-- UserMapper.xml -->
<select id="selectByCondition" resultType="com.example.User">
SELECT * FROM user
WHERE id = #{id}
AND name LIKE CONCAT('%', #{name}, '%')
AND password LIKE CONCAT('%', #{password}, '%')
</select>
java
// 调用
List<User> users = userMapper.selectByCondition(1L, "张", "123");
MyBatis-Plus
java
// 使用条件构造器组合查询
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getId, 1L)
.like(User::getName, "张")
.like(User::getPassword, "123");
List<User> users = userMapper.selectList(wrapper);
JPA
java
// Repository 接口
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByIdAndNameContainingAndPasswordContaining(
Long id, String name, String password);
}
// 调用
List<User> users = userRepository.findByIdAndNameContainingAndPasswordContaining(1L, "张", "123");
小结:从上面三个查询场景可以看出,JDBC 需要手写全部 SQL 和结果集映射;MyBatis 需要手写 SQL 但映射交给框架;MyBatis-Plus 通过条件构造器几乎零 SQL;JPA 则完全靠方法命名约定自动生成查询。实际项目中可根据对 SQL 控制力的需求灵活选型。







7. 用户表设计与登录注册实现
下面实现一个最简单的用户表,包含 Id、Username、Password、Create_time、Update_time 五个字段,并给出登录与注册的完整代码。
7.1 表结构设计
| 字段 | 类型 | 说明 |
|---|---|---|
| Id | 字符串(主键,uid 生成) | 主键,使用 UUID 生成 |
| Username | 字符串(Unique 唯一索引) | 用户名,唯一 |
| Password | 字符串 | 密码 |
| Create_time | Date 类型 | 创建时自动填写 |
| Update_time | Date 类型 | 创建时自动填写,更新时更新 |
sql
-- 建表语句
CREATE TABLE user (
Id VARCHAR(32) PRIMARY KEY COMMENT '主键,uid 生成',
Username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名,唯一索引',
Password VARCHAR(100) NOT NULL COMMENT '密码',
Create_time DATETIME NOT NULL COMMENT '创建时间,自动填写',
Update_time DATETIME NOT NULL COMMENT '更新时间,创建时填写,更新时更新'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
7.2 实体类(MyBatis-Plus 示例)
java
// 实体类
@Data
@TableName("user")
public class User {
@TableId(type = IdType.ASSIGN_UUID) // uid 生成主键
private String id;
private String username;
private String password;
@TableField(fill = FieldFill.INSERT) // 创建时自动填写
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE) // 创建时填写,更新时更新
private Date updateTime;
}
java
// 自动填充处理器
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
}
}
java
// Mapper 接口
public interface UserMapper extends BaseMapper<User> {
}
7.3 注册代码
java
// 注册:用户名唯一校验 + 密码加密 + 插入用户
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private PasswordEncoder passwordEncoder;
public boolean register(String username, String password) {
// 1. 校验用户名是否已存在
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername, username);
if (userMapper.selectCount(wrapper) > 0) {
return false; // 用户名已存在
}
// 2. 创建用户,密码加密存储
User user = new User();
user.setUsername(username);
user.setPassword(passwordEncoder.encode(password));
// 3. 插入数据库(Id 由 ASSIGN_UUID 自动生成,时间字段自动填充)
return userMapper.insert(user) > 0;
}
}
7.4 登录代码
java
// 登录:根据用户名查询,校验密码
public User login(String username, String password) {
// 1. 根据用户名查询用户
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername, username);
User user = userMapper.selectOne(wrapper);
// 2. 用户不存在或密码不匹配则登录失败
if (user == null || !passwordEncoder.matches(password, user.getPassword())) {
return null;
}
// 3. 登录成功,返回用户信息(实际项目中可在此生成 Token)
return user;
}
说明 :主键 Id 使用 IdType.ASSIGN_UUID 自动生成 uid;Username 在数据库层面加了 UNIQUE 唯一索引,注册时再配合代码校验保证唯一;Create_time 和 Update_time 通过 MetaObjectHandler 自动填充,创建时都写入当前时间,更新时只刷新 Update_time。







8. 修改密码与注销账号
下面补充两个最常见的账号操作:修改密码和注销账号,均基于 MyBatis-Plus 实现。
8.1 修改密码
java
// 修改密码:校验原密码 + 更新为新密码
public boolean changePassword(String userId, String oldPassword, String newPassword) {
// 1. 根据 id 查询用户
User user = userMapper.selectById(userId);
if (user == null) {
return false; // 用户不存在
}
// 2. 校验原密码是否正确
if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
return false; // 原密码错误
}
// 3. 更新为新密码(加密存储,Update_time 自动刷新)
User update = new User();
update.setId(userId);
update.setPassword(passwordEncoder.encode(newPassword));
return userMapper.updateById(update) > 0;
}
8.2 注销账号
java
// 注销账号:根据 id 删除用户
public boolean deleteUser(String userId) {
// 直接调用 BaseMapper 内置删除方法
return userMapper.deleteById(userId) > 0;
}
说明 :修改密码时先校验原密码,避免被他人恶意篡改;新密码同样使用 PasswordEncoder 加密后存储,Update_time 由 MetaObjectHandler 自动刷新。注销账号直接按主键 Id 删除即可,实际项目中可改为逻辑删除(@TableLogic)以保留历史数据。







