跳到主要内容

Java API 与 SQL 构建器

本章介绍 MyBatis 的 Java API 使用方法,包括 SqlSession 的详细用法和 SQL 构建器类。

SqlSession API

SqlSession 是 MyBatis 的核心接口,提供了执行 SQL 命令、获取 Mapper、管理事务等方法。理解 SqlSession 的完整 API 对于高效使用 MyBatis 至关重要。

SqlSession 的生命周期

SqlSession 是非线程安全的,每个线程应该有自己独立的 SqlSession 实例。最佳实践是在方法作用域内使用,用完立即关闭。

// 标准用法
try (SqlSession session = sqlSessionFactory.openSession()) {
// 使用 session
User user = session.selectOne("com.example.mapper.UserMapper.selectById", 1L);
session.commit(); // 如果有修改操作,需要提交
} // 自动关闭

创建 SqlSession

SqlSession 由 SqlSessionFactory 创建,可以配置不同参数:

// 创建 SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

// 方式一:创建非自动提交的 Session
SqlSession session = sqlSessionFactory.openSession();

// 方式二:创建自动提交的 Session
SqlSession session = sqlSessionFactory.openSession(true);

// 方式三:指定执行器类型
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);

// 方式四:指定执行器类型和自动提交
SqlSession session = sqlSessionFactory.openSession(ExecutorType.REUSE, true);

// 方式五:指定 Connection(手动控制事务)
Connection connection = dataSource.getConnection();
SqlSession session = sqlSessionFactory.openSession(connection);

执行器类型

类型说明适用场景
SIMPLE每次执行创建新的 Statement普通查询(默认)
REUSE重用 PreparedStatement频繁执行相同 SQL
BATCH批量执行,重用 Statement批量插入、更新

语句执行方法

SqlSession 提供了多种执行 SQL 语句的方法,分为查询、插入、更新、删除四类。

查询方法

selectOne - 查询单条记录

当确定结果只有一条时使用,返回单个对象。如果查询结果有多条,会抛出 TooManyResultsException

// 使用完整语句 ID
User user = session.selectOne("com.example.mapper.UserMapper.selectById", 1L);

// 返回 null 表示未找到
User user = session.selectOne("selectById", 1L);
if (user == null) {
System.out.println("用户不存在");
}

适用场景

  • 按主键查询
  • 按唯一索引字段查询
  • 确定 only 返回一条记录的查询

selectList - 查询列表

返回多条记录,如果没有匹配项返回空列表(不是 null)。

// 查询全部
List<User> users = session.selectList("com.example.mapper.UserMapper.selectAll");

// 带参数查询
List<User> users = session.selectList("selectByStatus", 1);

// 参数为对象
UserQuery query = new UserQuery();
query.setStatus(1);
query.setUsername("张");
List<User> users = session.selectList("selectByCondition", query);

selectMap - 查询并转为 Map

将结果集转换为 Map,指定某一列作为 key。

// 以 id 为 key,User 对象为 value
Map<Long, User> userMap = session.selectMap(
"com.example.mapper.UserMapper.selectAll",
"id" // 作为 key 的列名或属性名
);

// 使用示例
User user = userMap.get(1L);

// 以 username 为 key
Map<String, User> userMap = session.selectMap("selectAll", "username");
User user = userMap.get("zhangsan");

select - 使用 ResultHandler

对于需要自定义结果处理的场景,可以使用 ResultHandler。

// 使用 Lambda 表达式
List<String> emails = new ArrayList<>();
session.select("selectAll", (ResultHandler<User>) context -> {
User user = context.getResultObject();
if (user.getEmail() != null) {
emails.add(user.getEmail());
}
});

// 自定义 ResultHandler
public class UserEmailHandler implements ResultHandler<User> {
private final List<String> emails = new ArrayList<>();

@Override
public void handleResult(ResultContext<? extends User> context) {
User user = context.getResultObject();
emails.add(user.getEmail());
}

public List<String> getEmails() {
return emails;
}
}

UserEmailHandler handler = new UserEmailHandler();
session.select("selectAll", handler);
List<String> emails = handler.getEmails();

selectCursor - 游标查询

对于大数据量查询,使用游标可以避免一次性加载所有数据到内存。

// 游标实现了 Closeable 接口,需要关闭
try (Cursor<User> cursor = session.selectCursor("selectAll")) {
for (User user : cursor) {
// 逐条处理,不会一次性加载全部数据
processUser(user);
}
} catch (IOException e) {
throw new RuntimeException(e);
}

游标查询的特点

  • 惰性加载,逐条获取数据
  • 内存占用小,适合大数据量
  • 只能遍历一次
  • 必须在事务中使用

带分页的查询

// 使用 RowBounds 进行内存分页(不推荐大数据量使用)
int offset = 10;
int limit = 10;
RowBounds rowBounds = new RowBounds(offset, limit);

List<User> users = session.selectList("selectAll", null, rowBounds);

// 注意:RowBounds 是内存分页,会先查出所有数据再截取
// 生产环境建议使用数据库物理分页(如 PageHelper)

插入方法

insert - 插入单条记录

// 插入对象
User user = new User();
user.setUsername("test");
user.setPassword("123456");
user.setEmail("[email protected]");

int rows = session.insert("com.example.mapper.UserMapper.insert", user);

// 返回值为影响的行数
if (rows > 0) {
// 如果配置了 useGeneratedKeys,主键已回填
System.out.println("插入成功,ID: " + user.getId());
}

更新方法

update - 更新记录

User user = session.selectOne("selectById", 1L);
user.setEmail("[email protected]");

int rows = session.update("com.example.mapper.UserMapper.update", user);

if (rows > 0) {
System.out.println("更新成功");
}

// 带条件的更新
Map<String, Object> params = new HashMap<>();
params.put("status", 0);
params.put("ids", Arrays.asList(1L, 2L, 3L));

int rows = session.update("batchUpdateStatus", params);

删除方法

delete - 删除记录

// 删除单条
int rows = session.delete("deleteById", 1L);

// 批量删除
List<Long> ids = Arrays.asList(1L, 2L, 3L);
int rows = session.delete("deleteByIds", ids);

System.out.println("删除了 " + rows + " 条记录");

事务控制方法

SqlSession 提供了手动控制事务的方法,在没有使用 Spring 管理事务时非常重要。

commit - 提交事务

SqlSession session = sqlSessionFactory.openSession();  // 默认不自动提交

try {
UserMapper mapper = session.getMapper(UserMapper.class);

// 执行多个操作
mapper.insert(user1);
mapper.insert(user2);

// 提交事务
session.commit();
} catch (Exception e) {
// 发生异常回滚
session.rollback();
throw e;
} finally {
session.close();
}

// 强制提交(即使设置为自动提交也会提交)
session.commit(true);

rollback - 回滚事务

try {
session.insert("insert", user);

// 业务检查
if (!validateUser(user)) {
session.rollback();
return;
}

session.commit();
} finally {
session.close();
}

// 强制回滚(即使设置为自动提交也会回滚)
session.rollback(true);

getConnection - 获取 JDBC 连接

Connection connection = session.getConnection();

// 使用 JDBC 原生 API
PreparedStatement ps = connection.prepareStatement("SELECT * FROM user");
ResultSet rs = ps.executeQuery();

// 注意:手动获取的连接仍受 SqlSession 事务管理

clearCache - 清空缓存

// 清空一级缓存
session.clearCache();

// 场景:需要强制重新查询数据库时
User user1 = session.selectOne("selectById", 1L); // 查询数据库

session.clearCache(); // 清空缓存

User user2 = session.selectOne("selectById", 1L); // 再次查询数据库

Mapper 接口方法

相比直接使用 SqlSession 的字符串 ID,使用 Mapper 接口更加类型安全且符合面向对象思想。

getMapper - 获取 Mapper 代理

// 获取 Mapper 接口的代理实现
UserMapper userMapper = session.getMapper(UserMapper.class);

// 调用方法
User user = userMapper.selectById(1L);
List<User> users = userMapper.selectAll();

// 插入
User newUser = new User();
newUser.setUsername("test");
int rows = userMapper.insert(newUser);

Mapper 接口的优势

  1. 类型安全:编译时检查方法签名,避免字符串错误
  2. IDE 支持:代码提示、跳转、重构支持
  3. 可读性好:方法名清晰表达意图
  4. 易于维护:接口定义集中管理

Mapper 接口最佳实践

// Mapper 接口定义
public interface UserMapper {

// 方法名与 XML 中的 id 一致
User selectById(Long id);

List<User> selectAll();

List<User> selectByCondition(UserQuery query);

int insert(User user);

int update(User user);

int deleteById(Long id);
}

// 使用示例
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);

// 类型安全的方法调用
User user = mapper.selectById(1L);

session.commit();
}

批量操作

MyBatis 提供了高效的批量操作方式,特别适合大数据量的插入、更新场景。

使用 BATCH 执行器

// 创建批量执行器
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);

try {
UserMapper mapper = session.getMapper(UserMapper.class);

// 准备数据
List<User> users = prepareUsers(1000);

// 批量插入
for (User user : users) {
mapper.insert(user);
}

// 批量执行
session.commit();

// 可选:获取批量结果
List<BatchResult> batchResults = session.flushStatements();
for (BatchResult result : batchResults) {
System.out.println("更新行数: " + Arrays.toString(result.getUpdateCounts()));
}
} finally {
session.close();
}

BATCH vs 普通执行的对比

// 普通执行:每条语句都提交到数据库
SqlSession session = sqlSessionFactory.openSession();
for (int i = 0; i < 1000; i++) {
mapper.insert(users.get(i));
// 每次都执行 JDBC 操作
}
session.commit();
// 执行 1000 次 JDBC 操作

// BATCH 执行:批量提交
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
for (int i = 0; i < 1000; i++) {
mapper.insert(users.get(i));
// 只是添加到批处理队列
}
session.commit();
// 一次性提交到数据库

批量操作注意事项

SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);

try {
UserMapper mapper = session.getMapper(UserMapper.class);

for (User user : users) {
mapper.insert(user);
}

// 批量操作必须在事务中
session.commit(); // 或 session.flushStatements()

} catch (Exception e) {
session.rollback();
throw e;
} finally {
session.close();
}

性能建议

  • 批量操作前关闭自动提交
  • 适当分批处理,避免单批过大
  • 定期 flush 减少内存占用

SQL 构建器(SQL Builder)

MyBatis 3.5+ 提供了 SQL 构建器类,可以用 Java 代码动态构建 SQL 语句,特别适合注解开发或动态 SQL 复杂的场景。

SQL 类简介

org.apache.ibatis.jdbc.SQL 类提供了一种流畅的 API 来构建 SQL 语句,避免字符串拼接的繁琐和易错。

import org.apache.ibatis.jdbc.SQL;

// 构建 SELECT 语句
String sql = new SQL() {{
SELECT("id, username, email");
FROM("user");
WHERE("status = 1");
ORDER_BY("id DESC");
}}.toString();

// 生成:SELECT id, username, email FROM user WHERE (status = 1) ORDER BY id DESC

SELECT 语句构建

public String selectUserById() {
return new SQL() {{
SELECT("id, username, email, phone, status");
FROM("user");
WHERE("id = #{id}");
}}.toString();
}

// 带多个条件的查询
public String selectByCondition(UserQuery query) {
return new SQL() {{
SELECT("*");
FROM("user");
if (query.getUsername() != null) {
WHERE("username LIKE CONCAT('%', #{username}, '%')");
}
if (query.getEmail() != null) {
WHERE("email = #{email}");
}
if (query.getStatus() != null) {
WHERE("status = #{status}");
}
ORDER_BY("id DESC");
}}.toString();
}

// 多表关联查询
public String selectUserWithOrders() {
return new SQL() {{
SELECT("u.id, u.username, o.id as order_id, o.order_no");
FROM("user u");
LEFT_OUTER_JOIN("orders o ON u.id = o.user_id");
WHERE("u.status = 1");
}}.toString();
}

INSERT 语句构建

public String insertUser() {
return new SQL() {{
INSERT_INTO("user");
VALUES("username", "#{username}");
VALUES("password", "#{password}");
VALUES("email", "#{email}");
VALUES("phone", "#{phone}");
VALUES("status", "#{status}");
}}.toString();
}

// 动态插入(只插入非空字段)
public String insertSelective(User user) {
return new SQL() {{
INSERT_INTO("user");
if (user.getUsername() != null) {
VALUES("username", "#{username}");
}
if (user.getPassword() != null) {
VALUES("password", "#{password}");
}
if (user.getEmail() != null) {
VALUES("email", "#{email}");
}
}}.toString();
}

UPDATE 语句构建

public String updateUser() {
return new SQL() {{
UPDATE("user");
SET("username = #{username}");
SET("email = #{email}");
SET("phone = #{phone}");
WHERE("id = #{id}");
}}.toString();
}

// 动态更新
public String updateSelective(User user) {
return new SQL() {{
UPDATE("user");
if (user.getUsername() != null) {
SET("username = #{username}");
}
if (user.getEmail() != null) {
SET("email = #{email}");
}
if (user.getPhone() != null) {
SET("phone = #{phone}");
}
WHERE("id = #{id}");
}}.toString();
}

DELETE 语句构建

public String deleteById() {
return new SQL() {{
DELETE_FROM("user");
WHERE("id = #{id}");
}}.toString();
}

// 带条件的删除
public String deleteByCondition(UserQuery query) {
return new SQL() {{
DELETE_FROM("user");
WHERE("status = #{status}");
if (query.getCreateTime() != null) {
WHERE("create_time < #{createTime}");
}
}}.toString();
}

SQL 类方法一览

方法说明示例
SELECT(String... columns)SELECT 子句SELECT("id", "name")
SELECT_DISTINCT(String... columns)DISTINCT 查询SELECT_DISTINCT("user_id")
FROM(String table)FROM 子句FROM("user")
JOIN(String join)INNER JOINJOIN("orders o ON u.id = o.user_id")
LEFT_OUTER_JOIN(String join)LEFT JOINLEFT_OUTER_JOIN("orders o ON ...")
RIGHT_OUTER_JOIN(String join)RIGHT JOINRIGHT_OUTER_JOIN("orders o ON ...")
WHERE(String... conditions)WHERE 条件WHERE("id = #{id}")
OR()OR 条件WHERE("a = 1").OR().WHERE("b = 2")
AND()AND 条件WHERE("a = 1").AND().WHERE("b = 2")
GROUP_BY(String... columns)GROUP BYGROUP_BY("status")
HAVING(String... conditions)HAVING 条件HAVING("COUNT(*) > 1")
ORDER_BY(String... columns)ORDER BYORDER_BY("id DESC")
LIMIT(int limit)LIMITLIMIT(10)
OFFSET(int offset)OFFSETOFFSET(20)
INSERT_INTO(String table)INSERT 语句INSERT_INTO("user")
VALUES(String column, String value)VALUES 子句VALUES("name", "#{name}")
UPDATE(String table)UPDATE 语句UPDATE("user")
SET(String... sets)SET 子句SET("name = #{name}")
DELETE_FROM(String table)DELETE 语句DELETE_FROM("user")

结合 @SelectProvider 使用

SQL 构建器常与 Provider 注解配合使用:

// Provider 类
public class UserSqlProvider {

public String selectById() {
return new SQL() {{
SELECT("*");
FROM("user");
WHERE("id = #{id}");
}}.toString();
}

public String selectByCondition(UserQuery query) {
return new SQL() {{
SELECT("*");
FROM("user");
if (query.getUsername() != null) {
WHERE("username LIKE CONCAT('%', #{username}, '%')");
}
if (query.getStatus() != null) {
WHERE("status = #{status}");
}
ORDER_BY("id DESC");
}}.toString();
}

public String insertSelective(User user) {
return new SQL() {{
INSERT_INTO("user");
if (user.getUsername() != null) {
VALUES("username", "#{username}");
}
if (user.getPassword() != null) {
VALUES("password", "#{password}");
}
if (user.getEmail() != null) {
VALUES("email", "#{email}");
}
}}.toString();
}
}

// Mapper 接口
public interface UserMapper {

@SelectProvider(type = UserSqlProvider.class, method = "selectById")
User selectById(Long id);

@SelectProvider(type = UserSqlProvider.class, method = "selectByCondition")
List<User> selectByCondition(UserQuery query);

@InsertProvider(type = UserSqlProvider.class, method = "insertSelective")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertSelective(User user);
}

高级用法

使用 SQL 的链式调用

// SQL 类支持链式调用
String sql = new SQL()
.SELECT("id", "username", "email")
.FROM("user")
.WHERE("status = 1")
.ORDER_BY("id DESC")
.LIMIT(10)
.toString();

子查询

// 使用子查询需要手写 SQL 片段
public String selectWithSubquery() {
return new SQL() {{
SELECT("*");
FROM("user");
WHERE("id IN (SELECT user_id FROM orders WHERE amount > 1000)");
}}.toString();
}

复杂条件

public String complexCondition(UserQuery query) {
SQL sql = new SQL();
sql.SELECT("*").FROM("user");

// 复杂的 OR 条件
sql.WHERE("(status = 1 OR status = 2)");

if (query.getKeyword() != null) {
sql.WHERE("(username LIKE #{keyword} OR email LIKE #{keyword})");
}

return sql.toString();
}

实用示例

完整的 CRUD 示例

// SqlSession 操作
public class UserService {

private final SqlSessionFactory sqlSessionFactory;

public UserService(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}

// 查询单个
public User findById(Long id) {
try (SqlSession session = sqlSessionFactory.openSession()) {
return session.selectOne("com.example.mapper.UserMapper.selectById", id);
}
}

// 查询列表
public List<User> findByStatus(Integer status) {
try (SqlSession session = sqlSessionFactory.openSession()) {
return session.selectList("selectByStatus", status);
}
}

// 分页查询
public List<User> findByPage(int pageNum, int pageSize) {
try (SqlSession session = sqlSessionFactory.openSession()) {
RowBounds rowBounds = new RowBounds((pageNum - 1) * pageSize, pageSize);
return session.selectList("selectAll", null, rowBounds);
}
}

// 插入
public Long insert(User user) {
try (SqlSession session = sqlSessionFactory.openSession()) {
int rows = session.insert("insert", user);
session.commit();
return rows > 0 ? user.getId() : null;
}
}

// 更新
public boolean update(User user) {
try (SqlSession session = sqlSessionFactory.openSession()) {
int rows = session.update("update", user);
session.commit();
return rows > 0;
}
}

// 删除
public boolean delete(Long id) {
try (SqlSession session = sqlSessionFactory.openSession()) {
int rows = session.delete("deleteById", id);
session.commit();
return rows > 0;
}
}

// 批量插入
public int batchInsert(List<User> users) {
try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
for (User user : users) {
session.insert("insert", user);
}
session.commit();
return users.size();
}
}
}

使用游标处理大数据

public void processLargeData() {
try (SqlSession session = sqlSessionFactory.openSession()) {

// 开启事务(游标需要在事务中使用)
try (Cursor<User> cursor = session.selectCursor("selectAll")) {

int count = 0;
for (User user : cursor) {
// 处理数据
processUser(user);

// 批量处理示例:每 1000 条处理一次
if (++count % 1000 == 0) {
System.out.println("已处理 " + count + " 条");
}
}

System.out.println("处理完成,共 " + count + " 条");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}

最佳实践

1. 使用 try-with-resources

// 推荐:自动关闭 SqlSession
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
// 业务操作
session.commit();
}

// 不推荐:手动关闭
SqlSession session = null;
try {
session = sqlSessionFactory.openSession();
// 业务操作
session.commit();
} finally {
if (session != null) {
session.close();
}
}

2. 选择合适的执行器

// 普通查询:SIMPLE(默认)
SqlSession session = sqlSessionFactory.openSession();

// 批量操作:BATCH
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);

// 频繁执行相同 SQL:REUSE
SqlSession session = sqlSessionFactory.openSession(ExecutorType.REUSE);

3. 合理管理事务

// 不使用 Spring 时,手动管理事务
try (SqlSession session = sqlSessionFactory.openSession()) {
try {
UserMapper mapper = session.getMapper(UserMapper.class);

mapper.insert(user);
mapper.updateProfile(profile);

session.commit();
} catch (Exception e) {
session.rollback();
throw e;
}
}

4. 使用 Mapper 接口

// 推荐:使用 Mapper 接口
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectById(1L);

// 不推荐:使用字符串 ID
User user = session.selectOne("com.example.mapper.UserMapper.selectById", 1L);

5. 批量操作优化

// 批量插入
try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
UserMapper mapper = session.getMapper(UserMapper.class);

// 分批处理,避免内存溢出
int batchSize = 1000;
for (int i = 0; i < users.size(); i++) {
mapper.insert(users.get(i));

if ((i + 1) % batchSize == 0) {
session.flushStatements(); // 定期刷新
}
}

session.commit();
}

小结

本章详细介绍了 MyBatis 的 Java API:

  • SqlSession 核心 API:查询、插入、更新、删除方法
  • 事务控制:commit、rollback、clearCache
  • Mapper 接口:类型安全的数据库操作
  • 批量操作:BATCH 执行器的高效使用
  • SQL 构建器:使用 SQL 类动态构建 SQL

掌握这些 API 可以更加灵活地使用 MyBatis,处理各种复杂的数据操作场景。