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 接口的优势
- 类型安全:编译时检查方法签名,避免字符串错误
- IDE 支持:代码提示、跳转、重构支持
- 可读性好:方法名清晰表达意图
- 易于维护:接口定义集中管理
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 JOIN | JOIN("orders o ON u.id = o.user_id") |
LEFT_OUTER_JOIN(String join) | LEFT JOIN | LEFT_OUTER_JOIN("orders o ON ...") |
RIGHT_OUTER_JOIN(String join) | RIGHT JOIN | RIGHT_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 BY | GROUP_BY("status") |
HAVING(String... conditions) | HAVING 条件 | HAVING("COUNT(*) > 1") |
ORDER_BY(String... columns) | ORDER BY | ORDER_BY("id DESC") |
LIMIT(int limit) | LIMIT | LIMIT(10) |
OFFSET(int offset) | OFFSET | OFFSET(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,处理各种复杂的数据操作场景。